blob: 072b9775666e36c04611765f257d0cb8366eee2c [file]
/**
* 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 <sstream>
#include <boost/test/included/unit_test.hpp>
#include <boost/test/unit_test.hpp>
#include "Compiler.hh"
#include "Node.hh"
#include "ValidSchema.hh"
// Assert that empty defaults don't make json schema compilation violate bounds
// checks, as they did in AVRO-1853. Please note that on Linux bounds are only
// checked in Debug builds (CMAKE_BUILD_TYPE=Debug).
void testEmptyBytesDefault() {
std::string input = "{\n\
\"type\": \"record\",\n\
\"name\": \"testrecord\",\n\
\"fields\": [\n\
{\n\
\"name\": \"testbytes\",\n\
\"type\": \"bytes\",\n\
\"default\": \"\"\n\
}\n\
]\n\
}\n\
";
std::string expected = "{\n\
\"type\": \"record\",\n\
\"name\": \"testrecord\",\n\
\"fields\": [\n\
{\n\
\"name\": \"testbytes\",\n\
\"type\": \"bytes\",\n\
\"default\": \"\"\n\
}\n\
]\n\
}\n\
";
avro::ValidSchema schema = avro::compileJsonSchemaFromString(input);
std::ostringstream actual;
schema.toJson(actual);
BOOST_CHECK_EQUAL(expected, actual.str());
}
void test2dArray() {
std::string input = "{\n\
\"type\": \"array\",\n\
\"items\": {\n\
\"type\": \"array\",\n\
\"items\": \"double\"\n\
}\n\
}\n";
std::string expected = "{\n\
\"type\": \"array\",\n\
\"items\": {\n\
\"type\": \"array\",\n\
\"items\": \"double\"\n\
}\n\
}\n\
";
avro::ValidSchema schema = avro::compileJsonSchemaFromString(input);
std::ostringstream actual;
schema.toJson(actual);
BOOST_CHECK_EQUAL(expected, actual.str());
}
void testRecordWithNamedReference() {
std::string nestedSchema = "{\"name\":\"NestedRecord\",\"type\":\"record\",\"fields\":[{\"name\":\"stringField\",\"type\":\"string\"}]}";
// The root schema references the nested schema above by name only.
// This mimics tools that allow schemas to have references to other schemas.
std::string rootSchema = "{\"name\":\"RootRecord\",\"type\":\"record\",\"fields\":[{\"name\": \"nestedField\",\"type\":\"NestedRecord\"}]}";
// First compile the nested schema
avro::ValidSchema nestedRecord = avro::compileJsonSchemaFromString(nestedSchema);
// Create a map of named references
std::map<avro::Name, avro::ValidSchema> namedReferences;
namedReferences[avro::Name("NestedRecord")] = nestedRecord;
// Parse the root schema with named references
std::istringstream rootSchemaStream(rootSchema);
avro::ValidSchema rootRecord = avro::compileJsonSchemaWithNamedReferences(rootSchemaStream, namedReferences);
// Verify the schema was compiled correctly
BOOST_CHECK_EQUAL("RootRecord", rootRecord.root()->name().simpleName());
// Get the nested field and verify its type
const avro::NodePtr &rootNode = rootRecord.root();
BOOST_CHECK_EQUAL(avro::AVRO_RECORD, rootNode->type());
BOOST_CHECK_EQUAL(1, rootNode->leaves());
const avro::NodePtr &nestedFieldNode = rootNode->leafAt(0);
BOOST_CHECK_EQUAL("NestedRecord", nestedFieldNode->name().simpleName());
}
// Verify recursive schemas don't create shared_ptr cycles by ensuring the
// root node expires once the ValidSchema goes out of scope. Example: binary
// tree node with left/right as union of null and the node type itself.
void testRecursiveBinaryTreeWeakPtrExpires() {
std::weak_ptr<avro::Node> weakRoot;
{
const std::string schema = R"({
"type": "record",
"name": "Node",
"fields": [
{"name": "value", "type": "int"},
{"name": "left", "type": ["null", "Node"], "default": null},
{"name": "right", "type": ["null", "Node"], "default": null}
]
})";
avro::ValidSchema s = avro::compileJsonSchemaFromString(schema);
// Capture a weak reference to the root node while the schema is alive.
weakRoot = s.root();
// Optionally exercise the schema to ensure validation completed.
BOOST_CHECK_EQUAL(avro::AVRO_RECORD, s.root()->type());
BOOST_CHECK_EQUAL("Node", s.root()->name().simpleName());
}
// After the ValidSchema (and any strong references) go out of scope,
// the weak pointer must not be lockable if there are no cycles.
BOOST_CHECK(weakRoot.expired());
}
boost::unit_test::test_suite *
init_unit_test_suite(int /*argc*/, char * /*argv*/[]) {
using namespace boost::unit_test;
auto *ts = BOOST_TEST_SUITE("Avro C++ unit tests for Compiler.cc");
ts->add(BOOST_TEST_CASE(&testEmptyBytesDefault));
ts->add(BOOST_TEST_CASE(&test2dArray));
ts->add(BOOST_TEST_CASE(&testRecordWithNamedReference));
ts->add(BOOST_TEST_CASE(&testRecursiveBinaryTreeWeakPtrExpires));
return ts;
}