diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
commit | e6918187568dbd01842d8d1d2c808ce16a894239 (patch) | |
tree | 64f88b554b444a49f656b6c656111a145cbbaa28 /src/test/rgw/test_rgw_xml.cc | |
parent | Initial commit. (diff) | |
download | ceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip |
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/test/rgw/test_rgw_xml.cc')
-rw-r--r-- | src/test/rgw/test_rgw_xml.cc | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/src/test/rgw/test_rgw_xml.cc b/src/test/rgw/test_rgw_xml.cc new file mode 100644 index 000000000..fa9f21157 --- /dev/null +++ b/src/test/rgw/test_rgw_xml.cc @@ -0,0 +1,463 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "rgw_xml.h" +#include <gtest/gtest.h> +#include <list> +#include <stdexcept> + +struct NameAndStatus { + // these are sub-tags + std::string name; + bool status; + + // intrusive XML decoding API + bool decode_xml(XMLObj *obj) { + if (!RGWXMLDecoder::decode_xml("Name", name, obj, true)) { + // name is mandatory + return false; + } + if (!RGWXMLDecoder::decode_xml("Status", status, obj, false)) { + // status is optional and defaults to True + status = true; + } + return true; + } +}; + +struct Item { + // these are sub-tags + NameAndStatus name_and_status; + int value; + int extra_value; + + // these are attributes + std::string date; + std::string comment; + + // intrusive XML decoding API + bool decode_xml(XMLObj *obj) { + if (!RGWXMLDecoder::decode_xml("NameAndStatus", name_and_status, obj, true)) { + // name amd status are mandatory + return false; + } + if (!RGWXMLDecoder::decode_xml("Value", value, obj, true)) { + // value is mandatory + return false; + } + if (!RGWXMLDecoder::decode_xml("ExtraValue", extra_value, obj, false)) { + // extra value is optional and defaults to zero + extra_value = 0; + } + + // date attribute is optional + if (!obj->get_attr("Date", date)) { + date = "no date"; + } + // comment attribute is optional + if (!obj->get_attr("Comment", comment)) { + comment = "no comment"; + } + + return true; + } +}; + +struct Items { + // these are sub-tags + std::list<Item> item_list; + + // intrusive XML decoding API + bool decode_xml(XMLObj *obj) { + do_decode_xml_obj(item_list, "Item", obj); + return true; + } +}; + +// in case of non-intrusive decoding class +// hierarchy should reflect the XML hierarchy + +class NameXMLObj: public XMLObj { +protected: + void xml_handle_data(const char *s, int len) override { + // no need to set "data", setting "name" directly + value.append(s, len); + } + +public: + std::string value; + ~NameXMLObj() override = default; +}; + +class StatusXMLObj: public XMLObj { +protected: + void xml_handle_data(const char *s, int len) override { + std::istringstream is(std::string(s, len)); + is >> std::boolalpha >> value; + } + +public: + bool value; + ~StatusXMLObj() override = default; +}; + +class NameAndStatusXMLObj: public NameAndStatus, public XMLObj { +public: + ~NameAndStatusXMLObj() override = default; + + bool xml_end(const char *el) override { + XMLObjIter iter = find("Name"); + NameXMLObj* _name = static_cast<NameXMLObj*>(iter.get_next()); + if (!_name) { + // name is mandatory + return false; + } + name = _name->value; + iter = find("Status"); + StatusXMLObj* _status = static_cast<StatusXMLObj*>(iter.get_next()); + if (!_status) { + // status is optional and defaults to True + status = true; + } else { + status = _status->value; + } + return true; + } +}; + +class ItemXMLObj: public Item, public XMLObj { +public: + ~ItemXMLObj() override = default; + + bool xml_end(const char *el) override { + XMLObjIter iter = find("NameAndStatus"); + NameAndStatusXMLObj* _name_and_status = static_cast<NameAndStatusXMLObj*>(iter.get_next()); + if (!_name_and_status) { + // name and status are mandatory + return false; + } + name_and_status = *static_cast<NameAndStatus*>(_name_and_status); + iter = find("Value"); + XMLObj* _value = iter.get_next(); + if (!_value) { + // value is mandatory + return false; + } + try { + value = std::stoi(_value->get_data()); + } catch (const std::exception& e) { + return false; + } + iter = find("ExtraValue"); + XMLObj* _extra_value = iter.get_next(); + if (_extra_value) { + // extra value is optional but cannot contain garbage + try { + extra_value = std::stoi(_extra_value->get_data()); + } catch (const std::exception& e) { + return false; + } + } else { + // if not set, it defaults to zero + extra_value = 0; + } + + // date attribute is optional + if (!get_attr("Date", date)) { + date = "no date"; + } + // comment attribute is optional + if (!get_attr("Comment", comment)) { + comment = "no comment"; + } + + return true; + } +}; + +class ItemsXMLObj: public Items, public XMLObj { +public: + ~ItemsXMLObj() override = default; + + bool xml_end(const char *el) override { + XMLObjIter iter = find("Item"); + ItemXMLObj* item_ptr = static_cast<ItemXMLObj*>(iter.get_next()); + // mandatory to have at least one item + bool item_found = false; + while (item_ptr) { + item_list.push_back(*static_cast<Item*>(item_ptr)); + item_ptr = static_cast<ItemXMLObj*>(iter.get_next()); + item_found = true; + } + return item_found; + } +}; + +class ItemsXMLParser: public RGWXMLParser { + static const int MAX_NAME_LEN = 16; +public: + XMLObj *alloc_obj(const char *el) override { + if (strncmp(el, "Items", MAX_NAME_LEN) == 0) { + items = new ItemsXMLObj; + return items; + } else if (strncmp(el, "Item", MAX_NAME_LEN) == 0) { + return new ItemXMLObj; + } else if (strncmp(el, "NameAndStatus", MAX_NAME_LEN) == 0) { + return new NameAndStatusXMLObj; + } else if (strncmp(el, "Name", MAX_NAME_LEN) == 0) { + return new NameXMLObj; + } else if (strncmp(el, "Status", MAX_NAME_LEN) == 0) { + return new StatusXMLObj; + } + return nullptr; + } + // this is a pointer to the parsed results + ItemsXMLObj* items; +}; + +static const char* good_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<Items>" + "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>" + "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>" + "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>" + "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>" + "</Items>"; + +static const char* expected_output = "((hello,1),1,0),((world,1),2,99),((foo,1),3,0),((bar,0),4,42),"; + +std::string to_string(const Items& items) { + std::stringstream ss; + for (const auto& item : items.item_list) { + ss << "((" << item.name_and_status.name << "," << item.name_and_status.status << ")," << item.value << "," << item.extra_value << ")" << ","; + } + return ss.str(); +} + +std::string to_string_with_attributes(const Items& items) { + std::stringstream ss; + for (const auto& item : items.item_list) { + ss << "(" << item.date << "," << item.comment << ",(" << item.name_and_status.name << "," << item.name_and_status.status << ")," + << item.value << "," << item.extra_value << ")" << ","; + } + return ss.str(); +} + +TEST(TestParser, BasicParsing) +{ + ItemsXMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_TRUE(parser.parse(good_input, strlen(good_input), 1)); + ASSERT_EQ(parser.items->item_list.size(), 4U); + ASSERT_STREQ(to_string(*parser.items).c_str(), expected_output); +} + +static const char* malformed_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<Items>" + "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value><Item>" + "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>" + "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>" + "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>" + "</Items>"; + +TEST(TestParser, MalformedInput) +{ + ItemsXMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_FALSE(parser.parse(good_input, strlen(malformed_input), 1)); +} + +static const char* missing_value_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<Items>" + "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>" + "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>" + "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>" + "<Item><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>" + "</Items>"; + +TEST(TestParser, MissingMandatoryTag) +{ + ItemsXMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_FALSE(parser.parse(missing_value_input, strlen(missing_value_input), 1)); +} + +static const char* unknown_tag_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<Items>" + "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>" + "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>" + "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus><Kaboom>0</Kaboom></Item>" + "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>" + "<Kaboom>0</Kaboom>" + "</Items>"; + +TEST(TestParser, UnknownTag) +{ + ItemsXMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_TRUE(parser.parse(unknown_tag_input, strlen(unknown_tag_input), 1)); + ASSERT_EQ(parser.items->item_list.size(), 4U); + ASSERT_STREQ(to_string(*parser.items).c_str(), expected_output); +} + +static const char* invalid_value_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<Items>" + "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>" + "<Item><ExtraValue>kaboom</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>" + "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>" + "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>" + "</Items>"; + +TEST(TestParser, InvalidValue) +{ + ItemsXMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_FALSE(parser.parse(invalid_value_input, strlen(invalid_value_input), 1)); +} + +static const char* good_input1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<Items>" + "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>" + "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name>"; + +static const char* good_input2 = "</NameAndStatus><Value>2</Value></Item>" + "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>" + "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>" + "</Items>"; + +TEST(TestParser, MultipleChunks) +{ + ItemsXMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_TRUE(parser.parse(good_input1, strlen(good_input1), 0)); + ASSERT_TRUE(parser.parse(good_input2, strlen(good_input2), 1)); + ASSERT_EQ(parser.items->item_list.size(), 4U); + ASSERT_STREQ(to_string(*parser.items).c_str(), expected_output); +} + +static const char* input_with_attributes = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<Items>" + "<Item Date=\"Tue Dec 27 17:21:29 2011\" Kaboom=\"just ignore\">" + "<NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value>" + "</Item>" + "<Item Comment=\"hello world\">" + "<ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value>" + "</Item>" + "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>" + "<Item Comment=\"goodbye\" Date=\"Thu Feb 28 10:00:18 UTC 2019 \">" + "<Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus>" + "</Item>" + "</Items>"; + +static const char* expected_output_with_attributes = "(Tue Dec 27 17:21:29 2011,no comment,(hello,1),1,0)," + "(no date,hello world,(world,1),2,99)," + "(no date,no comment,(foo,1),3,0)," + "(Thu Feb 28 10:00:18 UTC 2019 ,goodbye,(bar,0),4,42),"; + +TEST(TestParser, Attributes) +{ + ItemsXMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_TRUE(parser.parse(input_with_attributes, strlen(input_with_attributes), 1)); + ASSERT_EQ(parser.items->item_list.size(), 4U); + ASSERT_STREQ(to_string_with_attributes(*parser.items).c_str(), + expected_output_with_attributes); +} + +TEST(TestDecoder, BasicParsing) +{ + RGWXMLDecoder::XMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_TRUE(parser.parse(good_input, strlen(good_input), 1)); + Items result; + ASSERT_NO_THROW({ + ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); + }); + ASSERT_EQ(result.item_list.size(), 4U); + ASSERT_STREQ(to_string(result).c_str(), expected_output); +} + +TEST(TestDecoder, MalfomedInput) +{ + RGWXMLDecoder::XMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_FALSE(parser.parse(good_input, strlen(malformed_input), 1)); +} + +TEST(TestDecoder, MissingMandatoryTag) +{ + RGWXMLDecoder::XMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_TRUE(parser.parse(missing_value_input, strlen(missing_value_input), 1)); + Items result; + ASSERT_ANY_THROW({ + ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); + }); +} + +TEST(TestDecoder, InvalidValue) +{ + RGWXMLDecoder::XMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_TRUE(parser.parse(invalid_value_input, strlen(invalid_value_input), 1)); + Items result; + ASSERT_ANY_THROW({ + ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); + }); +} + +TEST(TestDecoder, MultipleChunks) +{ + RGWXMLDecoder::XMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_TRUE(parser.parse(good_input1, strlen(good_input1), 0)); + ASSERT_TRUE(parser.parse(good_input2, strlen(good_input2), 1)); + Items result; + ASSERT_NO_THROW({ + ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); + }); + ASSERT_EQ(result.item_list.size(), 4U); + ASSERT_STREQ(to_string(result).c_str(), expected_output); +} + +TEST(TestDecoder, Attributes) +{ + RGWXMLDecoder::XMLParser parser; + ASSERT_TRUE(parser.init()); + ASSERT_TRUE(parser.parse(input_with_attributes, strlen(input_with_attributes), 1)); + Items result; + ASSERT_NO_THROW({ + ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result, &parser, true)); + }); + ASSERT_EQ(result.item_list.size(), 4U); + ASSERT_STREQ(to_string_with_attributes(result).c_str(), + expected_output_with_attributes); +} + +static const char* expected_xml_output = "<Items xmlns=\"https://www.ceph.com/doc/\">" + "<Item Order=\"0\"><NameAndStatus><Name>hello</Name><Status>True</Status></NameAndStatus><Value>0</Value></Item>" + "<Item Order=\"1\"><NameAndStatus><Name>hello</Name><Status>False</Status></NameAndStatus><Value>1</Value></Item>" + "<Item Order=\"2\"><NameAndStatus><Name>hello</Name><Status>True</Status></NameAndStatus><Value>2</Value></Item>" + "<Item Order=\"3\"><NameAndStatus><Name>hello</Name><Status>False</Status></NameAndStatus><Value>3</Value></Item>" + "<Item Order=\"4\"><NameAndStatus><Name>hello</Name><Status>True</Status></NameAndStatus><Value>4</Value></Item>" + "</Items>"; +TEST(TestEncoder, ListWithAttrsAndNS) +{ + XMLFormatter f; + const auto array_size = 5; + f.open_array_section_in_ns("Items", "https://www.ceph.com/doc/"); + for (auto i = 0; i < array_size; ++i) { + FormatterAttrs item_attrs("Order", std::to_string(i).c_str(), NULL); + f.open_object_section_with_attrs("Item", item_attrs); + f.open_object_section("NameAndStatus"); + encode_xml("Name", "hello", &f); + encode_xml("Status", (i%2 == 0), &f); + f.close_section(); + encode_xml("Value", i, &f); + f.close_section(); + } + f.close_section(); + std::stringstream ss; + f.flush(ss); + ASSERT_STREQ(ss.str().c_str(), expected_xml_output); +} + |