| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <boost/test/included/unit_test.hpp> |
| #include <iostream> |
| #include <memory> |
| #include <string> |
| |
| #include "Compiler.hh" |
| #include "Decoder.hh" |
| #include "Encoder.hh" |
| #include "Node.hh" |
| #include "Parser.hh" |
| #include "Schema.hh" |
| #include "SchemaResolution.hh" |
| #include "Serializer.hh" |
| #include "Stream.hh" |
| #include "ValidSchema.hh" |
| #include "Zigzag.hh" |
| #include "boost/make_shared.hpp" |
| #include "boost/shared_ptr.hpp" |
| #include "buffer/BufferPrint.hh" |
| #include "buffer/BufferStream.hh" |
| |
| #include "AvroSerialize.hh" |
| #include "CustomAttributes.hh" |
| #include "NodeConcepts.hh" |
| #include "NodeImpl.hh" |
| #include "Types.hh" |
| |
| |
| using namespace avro; |
| |
| static const uint8_t fixeddata[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; |
| |
| #ifdef max |
| #undef max |
| #endif |
| struct TestSchema { |
| TestSchema() = default; |
| |
| static void createExampleSchema() { |
| // First construct our complex data type: |
| avro::RecordSchema myRecord("complex"); |
| |
| // Now populate my record with fields (each field is another schema): |
| myRecord.addField("real", avro::DoubleSchema()); |
| myRecord.addField("imaginary", avro::DoubleSchema()); |
| |
| // The complex record is the same as used above, let's make a schema |
| // for an array of these record |
| |
| avro::ArraySchema complexArray(myRecord); |
| |
| avro::ValidSchema validComplexArray(complexArray); |
| validComplexArray.toJson(std::cout); |
| } |
| |
| void buildSchema() { |
| RecordSchema record("RootRecord"); |
| |
| CustomAttributes customAttributeLong; |
| customAttributeLong.addAttribute("extra_info_mylong", std::string("it's a long field")); |
| // Validate that adding a custom attribute with same name is not allowed |
| bool caught = false; |
| try { |
| customAttributeLong.addAttribute("extra_info_mylong", std::string("duplicate")); |
| } |
| catch(Exception &e) { |
| std::cout << "(intentional) exception: " << e.what() << '\n'; |
| caught = true; |
| } |
| BOOST_CHECK_EQUAL(caught, true); |
| // Add custom attribute for the field |
| record.addField("mylong", LongSchema(), customAttributeLong); |
| |
| IntSchema intSchema; |
| avro::MapSchema map = MapSchema(IntSchema()); |
| |
| record.addField("mymap", map); |
| |
| ArraySchema array = ArraySchema(DoubleSchema()); |
| |
| const std::string s("myarray"); |
| record.addField(s, array); |
| |
| EnumSchema myenum("ExampleEnum"); |
| myenum.addSymbol("zero"); |
| myenum.addSymbol("one"); |
| myenum.addSymbol("two"); |
| myenum.addSymbol("three"); |
| |
| caught = false; |
| try { |
| myenum.addSymbol("three"); |
| } catch (Exception &e) { |
| std::cout << "(intentional) exception: " << e.what() << '\n'; |
| caught = true; |
| } |
| BOOST_CHECK_EQUAL(caught, true); |
| |
| record.addField("myenum", myenum); |
| |
| UnionSchema onion; |
| onion.addType(NullSchema()); |
| onion.addType(map); |
| onion.addType(FloatSchema()); |
| |
| record.addField("myunion", onion); |
| |
| RecordSchema nestedRecord("NestedRecord"); |
| nestedRecord.addField("floatInNested", FloatSchema()); |
| |
| record.addField("nested", nestedRecord); |
| |
| record.addField("mybool", BoolSchema()); |
| FixedSchema fixed(16, "fixed16"); |
| record.addField("myfixed", fixed); |
| |
| caught = false; |
| try { |
| record.addField("mylong", LongSchema()); |
| } catch (Exception &e) { |
| std::cout << "(intentional) exception: " << e.what() << '\n'; |
| caught = true; |
| } |
| BOOST_CHECK_EQUAL(caught, true); |
| |
| CustomAttributes customAttributeLong2; |
| customAttributeLong2.addAttribute("extra_info_mylong2", |
| std::string("it's a long field")); |
| customAttributeLong2.addAttribute("more_info_mylong2", |
| std::string("it's still a long field")); |
| record.addField("mylong2", LongSchema(), customAttributeLong2); |
| |
| record.addField("anotherint", intSchema); |
| |
| schema_.setSchema(record); |
| } |
| |
| void checkNameLookup() const { |
| NodePtr node = schema_.root(); |
| |
| size_t index = 0; |
| bool found = node->nameIndex("mylongxxx", index); |
| BOOST_CHECK_EQUAL(found, false); |
| |
| found = node->nameIndex("mylong", index); |
| BOOST_CHECK_EQUAL(found, true); |
| BOOST_CHECK_EQUAL(index, 0U); |
| |
| found = node->nameIndex("mylong2", index); |
| BOOST_CHECK_EQUAL(found, true); |
| BOOST_CHECK_EQUAL(index, 8U); |
| |
| found = node->nameIndex("myenum", index); |
| BOOST_CHECK_EQUAL(found, true); |
| NodePtr enumNode = node->leafAt(index); |
| |
| found = enumNode->nameIndex("one", index); |
| BOOST_CHECK_EQUAL(found, true); |
| BOOST_CHECK_EQUAL(index, 1U); |
| |
| found = enumNode->nameIndex("three", index); |
| BOOST_CHECK_EQUAL(found, true); |
| BOOST_CHECK_EQUAL(index, 3U); |
| |
| found = enumNode->nameIndex("four", index); |
| BOOST_CHECK_EQUAL(found, false); |
| } |
| |
| template<typename Serializer> |
| void printUnion(Serializer &s, int path) { |
| s.writeUnion(path); |
| if (path == 0) { |
| std::cout << "Null in union\n"; |
| s.writeNull(); |
| } else if (path == 1) { |
| std::cout << "Map in union\n"; |
| s.writeMapBlock(2); |
| s.writeString("Foo"); |
| s.writeInt(16); |
| s.writeString("Bar"); |
| s.writeInt(17); |
| s.writeMapBlock(1); |
| s.writeString("FooBar"); |
| s.writeInt(18); |
| s.writeMapEnd(); |
| } else { |
| std::cout << "Float in union\n"; |
| s.writeFloat(200.); |
| } |
| } |
| |
| template<typename Serializer> |
| void writeEncoding(Serializer &s, int path) { |
| std::cout << "Record\n"; |
| s.writeRecord(); |
| s.writeInt(1000); |
| |
| std::cout << "Map\n"; |
| s.writeMapBlock(2); |
| s.writeString(std::string("Foo")); |
| s.writeInt(16); |
| s.writeString(std::string("Bar")); |
| s.writeInt(17); |
| s.writeMapEnd(); |
| |
| std::cout << "Array\n"; |
| s.writeArrayBlock(2); |
| s.writeDouble(100.0); |
| s.writeDouble(1000.0); |
| s.writeArrayEnd(); |
| |
| std::cout << "Enum\n"; |
| s.writeEnum(3); |
| |
| std::cout << "Union\n"; |
| printUnion(s, path); |
| |
| std::cout << "Record\n"; |
| s.writeRecord(); |
| s.writeFloat(-101.101f); |
| s.writeRecordEnd(); |
| |
| std::cout << "Bool\n"; |
| s.writeBool(true); |
| |
| std::cout << "Fixed16\n"; |
| |
| s.writeFixed(fixeddata); |
| |
| std::cout << "Long\n"; |
| s.writeLong(7010728798977672067LL); |
| |
| std::cout << "Int\n"; |
| s.writeInt(-3456); |
| s.writeRecordEnd(); |
| } |
| |
| void printEncoding() { |
| std::cout << "Encoding\n"; |
| Serializer<Writer> s; |
| writeEncoding(s, 0); |
| std::cout << s.buffer(); |
| } |
| |
| void printValidatingEncoding(int path) { |
| std::cout << "Validating Encoding " << path << "\n"; |
| Serializer<ValidatingWriter> s(schema_); |
| writeEncoding(s, path); |
| std::cout << s.buffer(); |
| } |
| |
| void saveValidatingEncoding(int path) { |
| std::ofstream out("test.avro"); |
| Serializer<ValidatingWriter> s(schema_); |
| writeEncoding(s, path); |
| InputBuffer buf = s.buffer(); |
| istream is(buf); |
| out << is.rdbuf(); |
| } |
| |
| void printNext(Parser<Reader> &p) { |
| // no-op printer |
| } |
| |
| static void printNext(Parser<ValidatingReader> &p) { |
| std::cout << "Next: \"" << nextType(p); |
| std::string recordName; |
| std::string fieldName; |
| if (currentRecordName(p, recordName)) { |
| std::cout << "\" record: \"" << recordName; |
| } |
| if (nextFieldName(p, fieldName)) { |
| std::cout << "\" field: \"" << fieldName; |
| } |
| std::cout << "\"\n"; |
| } |
| |
| template<typename Parser> |
| void readMap(Parser &p) { |
| int64_t size; |
| do { |
| printNext(p); |
| size = p.readMapBlockSize(); |
| std::cout << "Size " << size << '\n'; |
| for (int64_t i = 0; i < size; ++i) { |
| std::string key; |
| printNext(p); |
| p.readString(key); |
| printNext(p); |
| int32_t intval = p.readInt(); |
| std::cout << key << ":" << intval << '\n'; |
| } |
| } while (size != 0); |
| } |
| |
| template<typename Parser> |
| void readArray(Parser &p) { |
| int64_t size; |
| double d = 0.0; |
| do { |
| printNext(p); |
| size = p.readArrayBlockSize(); |
| std::cout << "Size " << size << '\n'; |
| for (int64_t i = 0; i < size; ++i) { |
| printNext(p); |
| d = p.readDouble(); |
| std::cout << i << ":" << d << '\n'; |
| } |
| } while (size != 0); |
| BOOST_CHECK_EQUAL(d, 1000.0); |
| } |
| |
| template<typename Parser> |
| void readNestedRecord(Parser &p) { |
| printNext(p); |
| p.readRecord(); |
| printNext(p); |
| float f = p.readFloat(); |
| std::cout << f << '\n'; |
| BOOST_CHECK_EQUAL(f, -101.101f); |
| p.readRecordEnd(); |
| } |
| |
| template<typename Parser> |
| void readFixed(Parser &p) { |
| |
| std::array<uint8_t, 16> input{}; |
| p.readFixed(input); |
| BOOST_CHECK_EQUAL(input.size(), 16U); |
| |
| for (int i = 0; i < 16; ++i) { |
| std::cout << static_cast<int>(input[i]) << ' '; |
| } |
| std::cout << '\n'; |
| } |
| |
| template<typename Parser> |
| void readData(Parser &p) { |
| printNext(p); |
| p.readRecord(); |
| |
| printNext(p); |
| int64_t longval = p.readLong(); |
| std::cout << longval << '\n'; |
| BOOST_CHECK_EQUAL(longval, 1000); |
| |
| readMap(p); |
| readArray(p); |
| |
| printNext(p); |
| longval = p.readEnum(); |
| std::cout << "Enum choice " << longval << '\n'; |
| |
| printNext(p); |
| longval = p.readUnion(); |
| std::cout << "Union path " << longval << '\n'; |
| readMap(p); |
| |
| readNestedRecord(p); |
| |
| printNext(p); |
| bool boolval = p.readBool(); |
| std::cout << boolval << '\n'; |
| BOOST_CHECK_EQUAL(boolval, true); |
| |
| printNext(p); |
| readFixed(p); |
| |
| printNext(p); |
| longval = p.readLong(); |
| std::cout << longval << '\n'; |
| BOOST_CHECK_EQUAL(longval, 7010728798977672067LL); |
| |
| printNext(p); |
| int32_t intval = p.readInt(); |
| std::cout << intval << '\n'; |
| BOOST_CHECK_EQUAL(intval, -3456); |
| p.readRecordEnd(); |
| } |
| |
| void readRawData() { |
| std::ifstream in("test.avro"); |
| ostream os; |
| os << in.rdbuf(); |
| Parser<Reader> p(os.getBuffer()); |
| readData(p); |
| } |
| |
| void readValidatedData() { |
| std::ifstream in("test.avro"); |
| ostream os; |
| os << in.rdbuf(); |
| Parser<ValidatingReader> p(schema_, os.getBuffer()); |
| readData(p); |
| } |
| |
| void testNodeRecord(const NodeRecord &nodeRecord, |
| const std::string &expectedJson) |
| { |
| BOOST_CHECK_EQUAL(nodeRecord.isValid(), true); |
| |
| std::ostringstream oss; |
| nodeRecord.printJson(oss, 0); |
| std::string actual = oss.str(); |
| actual.erase(std::remove_if(actual.begin(), actual.end(), |
| ::isspace), actual.end()); |
| |
| std::string expected = expectedJson; |
| expected.erase(std::remove_if(expected.begin(), expected.end(), |
| ::isspace), expected.end()); |
| |
| BOOST_CHECK_EQUAL(actual, expected); |
| } |
| |
| // Create NodeRecord with custom attributes at field level |
| // validate json serialization |
| void checkNodeRecordWithCustomAttribute() |
| { |
| Name recordName("Test"); |
| HasName nameConcept(recordName); |
| concepts::MultiAttribute<std::string> fieldNames; |
| concepts::MultiAttribute<NodePtr> fieldValues; |
| std::vector<GenericDatum> defaultValues; |
| concepts::MultiAttribute<CustomAttributes> customAttributes; |
| |
| CustomAttributes cf; |
| cf.addAttribute("stringField", std::string("\\\"field value with \\\"double quotes\\\"\\\"")); |
| cf.addAttribute("booleanField", std::string("true")); |
| cf.addAttribute("numberField", std::string("1.23")); |
| cf.addAttribute("nullField", std::string("null")); |
| cf.addAttribute("arrayField", std::string("[1]")); |
| cf.addAttribute("mapField", std::string("{\\\"key1\\\":\\\"value1\\\", \\\"key2\\\":\\\"value2\\\"}")); |
| fieldNames.add("f1"); |
| fieldValues.add(NodePtr( new NodePrimitive(Type::AVRO_LONG))); |
| customAttributes.add(cf); |
| |
| NodeRecord nodeRecordWithCustomAttribute(nameConcept, fieldValues, |
| fieldNames, defaultValues, |
| customAttributes); |
| std::string expectedJsonWithCustomAttribute = |
| "{\"type\": \"record\", \"name\": \"Test\",\"fields\": " |
| "[{\"name\": \"f1\", \"type\": \"long\", " |
| "\"arrayField\": \"[1]\", " |
| "\"booleanField\": \"true\", " |
| "\"mapField\": \"{\\\"key1\\\":\\\"value1\\\", \\\"key2\\\":\\\"value2\\\"}\", " |
| "\"nullField\": \"null\", " |
| "\"numberField\": \"1.23\", " |
| "\"stringField\": \"\\\"field value with \\\"double quotes\\\"\\\"\"" |
| "}]}"; |
| testNodeRecord(nodeRecordWithCustomAttribute, |
| expectedJsonWithCustomAttribute); |
| } |
| |
| // Create NodeRecord without custom attributes at field level |
| // validate json serialization |
| void checkNodeRecordWithoutCustomAttribute() |
| { |
| Name recordName("Test"); |
| HasName nameConcept(recordName); |
| concepts::MultiAttribute<std::string> fieldNames; |
| concepts::MultiAttribute<NodePtr> fieldValues; |
| std::vector<GenericDatum> defaultValues; |
| |
| fieldNames.add("f1"); |
| fieldValues.add(NodePtr( new NodePrimitive(Type::AVRO_LONG))); |
| |
| NodeRecord nodeRecordWithoutCustomAttribute(nameConcept, fieldValues, |
| fieldNames, defaultValues); |
| std::string expectedJsonWithoutCustomAttribute = |
| "{\"type\": \"record\", \"name\": \"Test\",\"fields\": " |
| "[{\"name\": \"f1\", \"type\": \"long\"}]}"; |
| testNodeRecord(nodeRecordWithoutCustomAttribute, |
| expectedJsonWithoutCustomAttribute); |
| } |
| |
| void checkCustomAttributes_getAttribute() |
| { |
| CustomAttributes cf; |
| cf.addAttribute("field1", std::string("1")); |
| |
| BOOST_CHECK_EQUAL(std::string("1"), *cf.getAttribute("field1")); |
| BOOST_CHECK_EQUAL(false, cf.getAttribute("not_existing").is_initialized()); |
| } |
| |
| void test() { |
| std::cout << "Before\n"; |
| schema_.toJson(std::cout); |
| schema_.toFlatList(std::cout); |
| buildSchema(); |
| std::cout << "After\n"; |
| schema_.toJson(std::cout); |
| schema_.toFlatList(std::cout); |
| |
| checkNameLookup(); |
| |
| printEncoding(); |
| printValidatingEncoding(0); |
| printValidatingEncoding(1); |
| printValidatingEncoding(2); |
| |
| saveValidatingEncoding(1); |
| readRawData(); |
| readValidatedData(); |
| |
| createExampleSchema(); |
| |
| checkNodeRecordWithoutCustomAttribute(); |
| checkNodeRecordWithCustomAttribute(); |
| checkCustomAttributes_getAttribute(); |
| } |
| |
| ValidSchema schema_; |
| }; |
| |
| struct TestEncoding { |
| |
| static void compare(int32_t val) { |
| uint32_t encoded = encodeZigzag32(val); |
| BOOST_CHECK_EQUAL(decodeZigzag32(encoded), val); |
| } |
| |
| static void compare(int64_t val) { |
| uint64_t encoded = encodeZigzag64(val); |
| BOOST_CHECK_EQUAL(decodeZigzag64(encoded), val); |
| } |
| |
| template<typename IntType> |
| void testEncoding(IntType start, IntType stop) { |
| std::cout << "testing from " << start << " to " << stop << " inclusive\n"; |
| IntType val = start; |
| IntType diff = stop - start + 1; |
| |
| for (IntType i = 0; i < diff; ++i) { |
| compare(val + i); |
| } |
| } |
| |
| template<typename IntType> |
| void testEncoding() { |
| testEncoding<IntType>(std::numeric_limits<IntType>::min(), std::numeric_limits<IntType>::min() + 1000); |
| testEncoding<IntType>(-1000, 1000); |
| testEncoding<IntType>(std::numeric_limits<IntType>::max() - 1000, std::numeric_limits<IntType>::max()); |
| } |
| |
| void test() { |
| testEncoding<int32_t>(); |
| testEncoding<int64_t>(); |
| } |
| }; |
| |
| struct TestNested { |
| TestNested() = default; |
| |
| void createSchema() { |
| std::cout << "TestNested\n"; |
| RecordSchema rec("LongListContainer"); |
| |
| RecordSchema list("LongList"); |
| list.addField("value", LongSchema()); |
| UnionSchema next; |
| next.addType(NullSchema()); |
| next.addType(SymbolicSchema(Name("LongList"), list.root())); |
| list.addField("next", next); |
| list.addField("end", BoolSchema()); |
| rec.addField("list", list); |
| |
| RecordSchema arrayTree("ArrayTree"); |
| arrayTree.addField("label", StringSchema()); |
| arrayTree.addField("children", ArraySchema(SymbolicSchema(Name("ArrayTree"), arrayTree.root()))); |
| rec.addField("array_tree", arrayTree); |
| |
| schema_.setSchema(rec); |
| schema_.toJson(std::cout); |
| schema_.toFlatList(std::cout); |
| } |
| |
| InputBuffer serializeNoRecurse() const { |
| std::cout << "No recurse\n"; |
| Serializer<ValidatingWriter> s(schema_); |
| s.writeRecord(); |
| { |
| s.writeRecord(); |
| s.writeLong(1); |
| s.writeUnion(0); |
| s.writeNull(); |
| s.writeBool(true); |
| s.writeRecordEnd(); |
| } |
| { |
| s.writeRecord(); |
| s.writeString("hello world"); |
| s.writeArrayEnd(); |
| s.writeRecordEnd(); |
| } |
| s.writeRecordEnd(); |
| |
| return s.buffer(); |
| } |
| |
| static void encodeNoRecurse(Encoder &e) { |
| std::cout << "Encode no recurse\n"; |
| e.encodeLong(1); |
| e.encodeUnionIndex(0); |
| e.encodeNull(); |
| e.encodeBool(true); |
| |
| e.encodeString("hello world"); |
| e.arrayStart(); |
| e.arrayEnd(); |
| } |
| |
| InputBuffer serializeRecurse() const { |
| std::cout << "Recurse\n"; |
| Serializer<ValidatingWriter> s(schema_); |
| s.writeRecord(); |
| { |
| s.writeRecord(); |
| s.writeLong(1); |
| s.writeUnion(1); |
| { |
| s.writeRecord(); |
| s.writeLong(2); |
| s.writeUnion(1); |
| { |
| s.writeRecord(); |
| s.writeLong(3); |
| s.writeUnion(0); |
| { s.writeNull(); } |
| s.writeBool(false); |
| s.writeRecordEnd(); |
| } |
| s.writeBool(false); |
| s.writeRecordEnd(); |
| } |
| s.writeBool(true); |
| s.writeRecordEnd(); |
| } |
| { |
| s.writeRecord(); |
| s.writeString("a"); |
| s.writeArrayBlock(2); |
| { |
| s.writeRecord(); |
| s.writeString("aa"); |
| s.writeArrayBlock(1); |
| { |
| s.writeRecord(); |
| s.writeString("aaa"); |
| s.writeArrayEnd(); |
| s.writeRecordEnd(); |
| } |
| s.writeArrayEnd(); |
| s.writeRecordEnd(); |
| } |
| { |
| s.writeRecord(); |
| s.writeString("ab"); |
| s.writeArrayEnd(); |
| s.writeRecordEnd(); |
| } |
| s.writeArrayEnd(); |
| s.writeRecordEnd(); |
| } |
| s.writeRecordEnd(); |
| |
| return s.buffer(); |
| } |
| |
| static void encodeRecurse(Encoder &e) { |
| std::cout << "Encode recurse\n"; |
| e.encodeLong(1); |
| e.encodeUnionIndex(1); |
| { |
| e.encodeLong(2); |
| e.encodeUnionIndex(1); |
| { |
| e.encodeLong(3); |
| e.encodeUnionIndex(0); |
| { |
| e.encodeNull(); |
| } |
| e.encodeBool(false); |
| } |
| e.encodeBool(false); |
| } |
| e.encodeBool(true); |
| |
| e.encodeString("a"); |
| e.arrayStart(); |
| e.setItemCount(2); |
| { |
| e.encodeString("aa"); |
| e.arrayStart(); |
| e.setItemCount(1); |
| { |
| e.encodeString("aaa"); |
| e.arrayStart(); |
| e.arrayEnd(); |
| } |
| e.arrayEnd(); |
| } |
| { |
| e.encodeString("ab"); |
| e.arrayStart(); |
| e.arrayEnd(); |
| } |
| e.arrayEnd(); |
| } |
| |
| void readRecord(Parser<ValidatingReader> &p) { |
| p.readRecord(); |
| readListRecord(p); |
| readArrayRecord(p); |
| p.readRecordEnd(); |
| } |
| |
| void readListRecord(Parser<ValidatingReader> &p) { |
| p.readRecord(); |
| int64_t val = p.readLong(); |
| std::cout << "longval = " << val << '\n'; |
| int64_t path = p.readUnion(); |
| if (path == 1) { |
| readListRecord(p); |
| } else { |
| p.readNull(); |
| } |
| bool b = p.readBool(); |
| std::cout << "bval = " << b << '\n'; |
| p.readRecordEnd(); |
| } |
| |
| void readArrayRecord(Parser<ValidatingReader> &p) { |
| p.readRecord(); |
| std::string label; |
| p.readString(label); |
| std::cout << "label = " << label << '\n'; |
| for (int64_t bs = p.readArrayBlockSize(); bs > 0; |
| bs = p.readArrayBlockSize()) { |
| for (int64_t i = 0; i < bs; ++i) { |
| readArrayRecord(p); |
| } |
| } |
| p.readRecordEnd(); |
| } |
| |
| void validatingParser(InputBuffer &buf) { |
| Parser<ValidatingReader> p(schema_, buf); |
| readRecord(p); |
| } |
| |
| void decodeListRecord(Decoder &d) { |
| int64_t val = d.decodeLong(); |
| std::cout << "longval = " << val << '\n'; |
| int64_t path = d.decodeUnionIndex(); |
| if (path == 1) { |
| decodeListRecord(d); |
| } else { |
| d.decodeNull(); |
| } |
| bool b = d.decodeBool(); |
| std::cout << "bval = " << b << '\n'; |
| } |
| |
| void decodeArrayRecord(Decoder &d) { |
| std::string label = d.decodeString(); |
| std::cout << "label = " << label << '\n'; |
| for (int64_t bs = d.arrayStart(); bs > 0; bs = d.arrayNext()) { |
| std::cout << "array block size = " << bs << '\n'; |
| for (int64_t i = 0; i < bs; ++i) { |
| decodeArrayRecord(d); |
| } |
| } |
| } |
| |
| void runDecoder(Decoder &d) { |
| decodeListRecord(d); |
| decodeArrayRecord(d); |
| } |
| |
| void testToScreen() const { |
| InputBuffer buf1 = serializeNoRecurse(); |
| InputBuffer buf2 = serializeRecurse(); |
| std::cout << buf1; |
| std::cout << buf2; |
| } |
| |
| // Tests for Serializer + Parser |
| void testParseNoRecurse() { |
| std::cout << "ParseNoRecurse\n"; |
| InputBuffer buf = serializeNoRecurse(); |
| |
| validatingParser(buf); |
| } |
| |
| void testParseRecurse() { |
| std::cout << "ParseRecurse\n"; |
| InputBuffer buf = serializeRecurse(); |
| |
| validatingParser(buf); |
| } |
| |
| // Tests for encode + decode |
| void runEncodeDecode(Encoder &e, Decoder &d, void (*encode_fn)(Encoder &)) { |
| std::unique_ptr<OutputStream> out = memoryOutputStream(); |
| e.init(*out); |
| encode_fn(e); |
| std::unique_ptr<InputStream> in = memoryInputStream(*out); |
| d.init(*in); |
| runDecoder(d); |
| } |
| |
| void testDecodeNoRecurse() { |
| std::cout << "DecodeNoRecurse\n"; |
| runEncodeDecode(*validatingEncoder(schema_, binaryEncoder()), |
| *validatingDecoder(schema_, binaryDecoder()), |
| encodeNoRecurse); |
| } |
| |
| void testDecodeRecurse() { |
| std::cout << "DecodeRecurse\n"; |
| runEncodeDecode(*validatingEncoder(schema_, binaryEncoder()), |
| *validatingDecoder(schema_, binaryDecoder()), |
| encodeRecurse); |
| } |
| |
| void testDecodeNoRecurseJson() { |
| std::cout << "DecodeNoRecurseJson\n"; |
| runEncodeDecode(*jsonEncoder(schema_), |
| *jsonDecoder(schema_), |
| encodeNoRecurse); |
| } |
| |
| void testDecodeRecurseJson() { |
| std::cout << "DecodeRecurseJson\n"; |
| runEncodeDecode(*jsonEncoder(schema_), |
| *jsonDecoder(schema_), |
| encodeRecurse); |
| } |
| |
| void test() { |
| createSchema(); |
| testToScreen(); |
| |
| testParseNoRecurse(); |
| testParseRecurse(); |
| |
| testDecodeNoRecurse(); |
| testDecodeRecurse(); |
| testDecodeNoRecurseJson(); |
| testDecodeRecurseJson(); |
| } |
| |
| ValidSchema schema_; |
| }; |
| |
| struct TestGenerated { |
| TestGenerated() = default; |
| |
| void test() { |
| std::cout << "TestGenerated\n"; |
| |
| int32_t val = 100; |
| float f = 200.0; |
| |
| Writer writer; |
| |
| serialize(writer, val); |
| serialize(writer, Null()); |
| serialize(writer, f); |
| |
| std::cout << writer.buffer(); |
| } |
| }; |
| |
| struct TestBadStuff { |
| void testBadFile() { |
| std::cout << "TestBadFile\n"; |
| |
| avro::ValidSchema schema; |
| std::ifstream in("agjoewejefkjs"); |
| std::string error; |
| bool result = avro::compileJsonSchema(in, schema, error); |
| BOOST_CHECK_EQUAL(result, false); |
| std::cout << "(intentional) error: " << error << '\n'; |
| } |
| |
| void testBadSchema() { |
| std::cout << "TestBadSchema\n"; |
| |
| std::string str(R"({ "type" : "wrong" })"); |
| std::istringstream in(str); |
| |
| avro::ValidSchema schema; |
| std::string error; |
| bool result = avro::compileJsonSchema(in, schema, error); |
| BOOST_CHECK_EQUAL(result, false); |
| std::cout << "(intentional) error: " << error << '\n'; |
| } |
| |
| void test() { |
| std::cout << "TestBadStuff\n"; |
| testBadFile(); |
| testBadSchema(); |
| } |
| }; |
| |
| struct TestResolution { |
| TestResolution() : int_(IntSchema()), |
| long_(LongSchema()), |
| bool_(BoolSchema()), |
| float_(FloatSchema()), |
| double_(DoubleSchema()), |
| |
| mapOfInt_(MapSchema(IntSchema())), |
| mapOfDouble_(MapSchema(DoubleSchema())), |
| |
| arrayOfLong_(ArraySchema(LongSchema())), |
| arrayOfFloat_(ArraySchema(FloatSchema())) { |
| { |
| EnumSchema one("one"); |
| one.addSymbol("X"); |
| enumOne_.setSchema(one); |
| |
| EnumSchema two("two"); |
| two.addSymbol("Y"); |
| enumTwo_.setSchema(two); |
| } |
| |
| { |
| UnionSchema one; |
| one.addType(IntSchema()); |
| one.addType(FloatSchema()); |
| unionOne_.setSchema(one); |
| |
| UnionSchema two; |
| two.addType(IntSchema()); |
| two.addType(DoubleSchema()); |
| unionTwo_.setSchema(two); |
| } |
| } |
| |
| SchemaResolution resolve(const ValidSchema &writer, const ValidSchema &reader) { |
| return writer.root()->resolve(*reader.root()); |
| } |
| |
| void test() { |
| std::cout << "TestResolution\n"; |
| |
| BOOST_CHECK_EQUAL(resolve(long_, long_), RESOLVE_MATCH); |
| BOOST_CHECK_EQUAL(resolve(long_, bool_), RESOLVE_NO_MATCH); |
| BOOST_CHECK_EQUAL(resolve(bool_, long_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(int_, long_), RESOLVE_PROMOTABLE_TO_LONG); |
| BOOST_CHECK_EQUAL(resolve(long_, int_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(int_, float_), RESOLVE_PROMOTABLE_TO_FLOAT); |
| BOOST_CHECK_EQUAL(resolve(float_, int_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(int_, double_), RESOLVE_PROMOTABLE_TO_DOUBLE); |
| BOOST_CHECK_EQUAL(resolve(double_, int_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(long_, float_), RESOLVE_PROMOTABLE_TO_FLOAT); |
| BOOST_CHECK_EQUAL(resolve(float_, long_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(long_, double_), RESOLVE_PROMOTABLE_TO_DOUBLE); |
| BOOST_CHECK_EQUAL(resolve(double_, long_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(float_, double_), RESOLVE_PROMOTABLE_TO_DOUBLE); |
| BOOST_CHECK_EQUAL(resolve(double_, float_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(int_, mapOfInt_), RESOLVE_NO_MATCH); |
| BOOST_CHECK_EQUAL(resolve(mapOfInt_, int_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(mapOfInt_, mapOfInt_), RESOLVE_MATCH); |
| BOOST_CHECK_EQUAL(resolve(mapOfDouble_, mapOfInt_), RESOLVE_NO_MATCH); |
| BOOST_CHECK_EQUAL(resolve(mapOfInt_, mapOfDouble_), RESOLVE_PROMOTABLE_TO_DOUBLE); |
| |
| BOOST_CHECK_EQUAL(resolve(long_, arrayOfLong_), RESOLVE_NO_MATCH); |
| BOOST_CHECK_EQUAL(resolve(arrayOfLong_, long_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(arrayOfLong_, arrayOfLong_), RESOLVE_MATCH); |
| BOOST_CHECK_EQUAL(resolve(arrayOfFloat_, arrayOfLong_), RESOLVE_NO_MATCH); |
| BOOST_CHECK_EQUAL(resolve(arrayOfLong_, arrayOfFloat_), RESOLVE_PROMOTABLE_TO_FLOAT); |
| |
| BOOST_CHECK_EQUAL(resolve(enumOne_, enumOne_), RESOLVE_MATCH); |
| BOOST_CHECK_EQUAL(resolve(enumOne_, enumTwo_), RESOLVE_NO_MATCH); |
| |
| BOOST_CHECK_EQUAL(resolve(float_, unionOne_), RESOLVE_MATCH); |
| BOOST_CHECK_EQUAL(resolve(double_, unionOne_), RESOLVE_NO_MATCH); |
| BOOST_CHECK_EQUAL(resolve(float_, unionTwo_), RESOLVE_PROMOTABLE_TO_DOUBLE); |
| |
| BOOST_CHECK_EQUAL(resolve(unionOne_, float_), RESOLVE_MATCH); |
| BOOST_CHECK_EQUAL(resolve(unionOne_, double_), RESOLVE_PROMOTABLE_TO_DOUBLE); |
| BOOST_CHECK_EQUAL(resolve(unionTwo_, float_), RESOLVE_PROMOTABLE_TO_FLOAT); |
| BOOST_CHECK_EQUAL(resolve(unionOne_, unionTwo_), RESOLVE_MATCH); |
| } |
| |
| private: |
| ValidSchema int_; |
| ValidSchema long_; |
| ValidSchema bool_; |
| ValidSchema float_; |
| ValidSchema double_; |
| |
| ValidSchema mapOfInt_; |
| ValidSchema mapOfDouble_; |
| |
| ValidSchema arrayOfLong_; |
| ValidSchema arrayOfFloat_; |
| |
| ValidSchema enumOne_; |
| ValidSchema enumTwo_; |
| |
| ValidSchema unionOne_; |
| ValidSchema unionTwo_; |
| }; |
| |
| void testNestedArraySchema() { |
| ArraySchema b0 = ArraySchema(NullSchema()); |
| ArraySchema a0 = ArraySchema(b0); |
| |
| avro::ValidSchema vs(a0); |
| std::ostringstream actual; |
| vs.toJson(actual); |
| |
| std::string expected = "{\n\ |
| \"type\": \"array\",\n\ |
| \"items\": {\n\ |
| \"type\": \"array\",\n\ |
| \"items\": \"null\"\n\ |
| }\n\ |
| }\n"; |
| BOOST_CHECK_EQUAL(expected, actual.str()); |
| } |
| |
| void testNestedMapSchema() { |
| MapSchema b0 = MapSchema(NullSchema()); |
| MapSchema a0 = MapSchema(b0); |
| |
| avro::ValidSchema vs(a0); |
| std::ostringstream actual; |
| vs.toJson(actual); |
| |
| std::string expected = "{\n\ |
| \"type\": \"map\",\n\ |
| \"values\": {\n\ |
| \"type\": \"map\",\n\ |
| \"values\": \"null\"\n\ |
| }\n\ |
| }\n"; |
| BOOST_CHECK_EQUAL(expected, actual.str()); |
| } |
| |
| boost::unit_test::test_suite * |
| init_unit_test_suite(int /*argc*/, char * /*argv*/[]) { |
| using namespace boost::unit_test; |
| |
| auto *test = BOOST_TEST_SUITE("Avro C++ unit test suite"); |
| |
| test->add(BOOST_CLASS_TEST_CASE(&TestEncoding::test, |
| boost::make_shared<TestEncoding>())); |
| test->add(BOOST_CLASS_TEST_CASE(&TestSchema::test, |
| boost::make_shared<TestSchema>())); |
| test->add(BOOST_CLASS_TEST_CASE(&TestNested::test, |
| boost::make_shared<TestNested>())); |
| test->add(BOOST_CLASS_TEST_CASE(&TestGenerated::test, |
| boost::make_shared<TestGenerated>())); |
| test->add(BOOST_CLASS_TEST_CASE(&TestBadStuff::test, |
| boost::make_shared<TestBadStuff>())); |
| test->add(BOOST_CLASS_TEST_CASE(&TestResolution::test, |
| boost::make_shared<TestResolution>())); |
| test->add(BOOST_TEST_CASE(&testNestedArraySchema)); |
| test->add(BOOST_TEST_CASE(&testNestedMapSchema)); |
| |
| return test; |
| } |