blob: fde018b7baa44ca81f9ed7a0b5ec0c01a6316e99 [file] [log] [blame]
/**
* 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
*
* http://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.
*/
package org.apache.avro;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Collection;
import org.apache.avro.Schema.Type;
import org.apache.avro.Schema.Field;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.Encoder;
import org.apache.avro.io.EncoderFactory;
import org.apache.avro.io.JsonDecoder;
import org.apache.avro.compiler.specific.TestSpecificCompiler;
import org.apache.avro.util.Utf8;
public class TestSchema {
public static final String LISP_SCHEMA = "{\"type\": \"record\", \"name\": \"Lisp\", \"fields\": ["
+"{\"name\":\"value\", \"type\":[\"null\", \"string\","
+"{\"type\": \"record\", \"name\": \"Cons\", \"fields\": ["
+"{\"name\":\"car\", \"type\":\"Lisp\"},"
+"{\"name\":\"cdr\", \"type\":\"Lisp\"}]}]}]}";
public static final String BASIC_ENUM_SCHEMA = "{\"type\":\"enum\", \"name\":\"Test\","
+"\"symbols\": [\"A\", \"B\"]}";
public static final String SCHEMA_WITH_DOC_TAGS = "{\n"
+ " \"type\": \"record\",\n"
+ " \"name\": \"outer_record\",\n"
+ " \"doc\": \"This is not a world record.\",\n"
+ " \"fields\": [\n"
+ " { \"type\": { \"type\": \"fixed\", \"doc\": \"Very Inner Fixed\", "
+ " \"name\": \"very_inner_fixed\", \"size\": 1 },\n"
+ " \"doc\": \"Inner Fixed\", \"name\": \"inner_fixed\" },\n"
+ " { \"type\": \"string\",\n"
+ " \"name\": \"inner_string\",\n"
+ " \"doc\": \"Inner String\" },\n"
+ " { \"type\": { \"type\": \"enum\", \"doc\": \"Very Inner Enum\", \n"
+ " \"name\": \"very_inner_enum\", \n"
+ " \"symbols\": [ \"A\", \"B\", \"C\" ] },\n"
+ " \"doc\": \"Inner Enum\", \"name\": \"inner_enum\" },\n"
+ " { \"type\": [\"string\", \"int\"], \"doc\": \"Inner Union\", \n"
+ " \"name\": \"inner_union\" }\n" + " ]\n" + "}\n";
private static final int COUNT =
Integer.parseInt(System.getProperty("test.count", "10"));
@Test
public void testNull() throws Exception {
assertEquals(Schema.create(Type.NULL), Schema.parse("\"null\""));
assertEquals(Schema.create(Type.NULL), Schema.parse("{\"type\":\"null\"}"));
check("\"null\"", "null", null);
}
@Test
public void testBoolean() throws Exception {
assertEquals(Schema.create(Type.BOOLEAN), Schema.parse("\"boolean\""));
assertEquals(Schema.create(Type.BOOLEAN),
Schema.parse("{\"type\":\"boolean\"}"));
check("\"boolean\"", "true", Boolean.TRUE);
}
@Test
public void testString() throws Exception {
assertEquals(Schema.create(Type.STRING), Schema.parse("\"string\""));
assertEquals(Schema.create(Type.STRING),
Schema.parse("{\"type\":\"string\"}"));
check("\"string\"", "\"foo\"", new Utf8("foo"));
}
@Test
public void testBytes() throws Exception {
assertEquals(Schema.create(Type.BYTES), Schema.parse("\"bytes\""));
assertEquals(Schema.create(Type.BYTES),
Schema.parse("{\"type\":\"bytes\"}"));
check("\"bytes\"", "\"\\u0000ABC\\u00FF\"",
ByteBuffer.wrap(new byte[]{0,65,66,67,-1}));
}
@Test
public void testInt() throws Exception {
assertEquals(Schema.create(Type.INT), Schema.parse("\"int\""));
assertEquals(Schema.create(Type.INT), Schema.parse("{\"type\":\"int\"}"));
check("\"int\"", "9", new Integer(9));
}
@Test
public void testLong() throws Exception {
assertEquals(Schema.create(Type.LONG), Schema.parse("\"long\""));
assertEquals(Schema.create(Type.LONG), Schema.parse("{\"type\":\"long\"}"));
check("\"long\"", "11", new Long(11));
}
@Test
public void testFloat() throws Exception {
assertEquals(Schema.create(Type.FLOAT), Schema.parse("\"float\""));
assertEquals(Schema.create(Type.FLOAT),
Schema.parse("{\"type\":\"float\"}"));
check("\"float\"", "1.1", new Float(1.1));
}
@Test
public void testDouble() throws Exception {
assertEquals(Schema.create(Type.DOUBLE), Schema.parse("\"double\""));
assertEquals(Schema.create(Type.DOUBLE),
Schema.parse("{\"type\":\"double\"}"));
check("\"double\"", "1.2", new Double(1.2));
}
@Test
public void testArray() throws Exception {
String json = "{\"type\":\"array\", \"items\": \"long\"}";
Schema schema = Schema.parse(json);
Collection<Long> array = new GenericData.Array<Long>(1, schema);
array.add(1L);
check(json, "[1]", array);
array = new ArrayList<Long>(1);
array.add(1L);
check(json, "[1]", array);
checkParseError("{\"type\":\"array\"}"); // items required
}
@Test
public void testMap() throws Exception {
HashMap<Utf8,Long> map = new HashMap<Utf8,Long>();
map.put(new Utf8("a"), 1L);
check("{\"type\":\"map\", \"values\":\"long\"}", "{\"a\":1}", map);
checkParseError("{\"type\":\"map\"}"); // values required
}
@Test
public void testRecord() throws Exception {
String recordJson = "{\"type\":\"record\", \"name\":\"Test\", \"fields\":"
+"[{\"name\":\"f\", \"type\":\"long\", \"foo\":\"bar\"}]}";
Schema schema = Schema.parse(recordJson);
// test field props
assertEquals("bar", schema.getField("f").getProp("foo"));
assertEquals("bar", Schema.parse(schema.toString())
.getField("f").getProp("foo"));
schema.getField("f").addProp("baz", "boo");
assertEquals("boo", schema.getField("f").getProp("baz"));
GenericData.Record record = new GenericData.Record(schema);
record.put("f", 11L);
check(recordJson, "{\"f\":11}", record, false);
checkParseError("{\"type\":\"record\"}");
checkParseError("{\"type\":\"record\",\"name\":\"X\"}");
checkParseError("{\"type\":\"record\",\"name\":\"X\",\"fields\":\"Y\"}");
checkParseError("{\"type\":\"record\",\"name\":\"X\",\"fields\":"
+"[{\"name\":\"f\"}]}"); // no type
checkParseError("{\"type\":\"record\",\"name\":\"X\",\"fields\":"
+"[{\"type\":\"long\"}]}"); // no name
// check invalid record names
checkParseError("{\"type\":\"record\",\"name\":\"1X\",\"fields\":[]}");
checkParseError("{\"type\":\"record\",\"name\":\"X$\",\"fields\":[]}");
// check invalid field names
checkParseError("{\"type\":\"record\",\"name\":\"X\",\"fields\":["
+"{\"name\":\"1f\",\"type\":\"int\"}]}");
checkParseError("{\"type\":\"record\",\"name\":\"X\",\"fields\":["
+"{\"name\":\"f$\",\"type\":\"int\"}]}");
checkParseError("{\"type\":\"record\",\"name\":\"X\",\"fields\":["
+"{\"name\":\"f.g\",\"type\":\"int\"}]}");
}
@Test
public void testEnum() throws Exception {
check(BASIC_ENUM_SCHEMA, "\"B\"",
new GenericData.EnumSymbol(Schema.parse(BASIC_ENUM_SCHEMA), "B"),
false);
checkParseError("{\"type\":\"enum\"}"); // symbols required
checkParseError("{\"type\":\"enum\",\"symbols\": [\"X\"]}"); // name reqd
// check no duplicate symbols
checkParseError("{\"type\":\"enum\",\"name\":\"X\",\"symbols\":[\"X\",\"X\"]}");
// check no invalid symbols
checkParseError("{\"type\":\"enum\",\"name\":\"X\",\"symbols\":[\"1X\"]}");
checkParseError("{\"type\":\"enum\",\"name\":\"X\",\"symbols\":[\"X$\"]}");
checkParseError("{\"type\":\"enum\",\"name\":\"X\",\"symbols\":[\"X.Y\"]}");
}
@Test
public void testFixed() throws Exception {
String json = "{\"type\": \"fixed\", \"name\":\"Test\", \"size\": 1}";
Schema schema = Schema.parse(json);
check(json, "\"a\"",
new GenericData.Fixed(schema, new byte[]{(byte)'a'}), false);
checkParseError("{\"type\":\"fixed\"}"); // size required
}
@Test
public void testRecursive() throws Exception {
check("{\"type\": \"record\", \"name\": \"Node\", \"fields\": ["
+"{\"name\":\"label\", \"type\":\"string\"},"
+"{\"name\":\"children\", \"type\":"
+"{\"type\": \"array\", \"items\": \"Node\" }}]}",
false);
}
@Test
public void testRecursiveEquals() throws Exception {
String jsonSchema = "{\"type\":\"record\", \"name\":\"List\", \"fields\": ["
+"{\"name\":\"next\", \"type\":\"List\"}]}";
Schema s1 = Schema.parse(jsonSchema);
Schema s2 = Schema.parse(jsonSchema);
assertEquals(s1, s2);
s1.hashCode(); // test no stackoverflow
}
@Test
/** Test that equals() and hashCode() don't require exponential time on
* certain pathological schemas. */
public void testSchemaExplosion() throws Exception {
for (int i = 1; i < 15; i++) { // 15 is big enough to trigger
// create a list of records, each with a single field whose type is a
// union of all of the records.
List<Schema> recs = new ArrayList<Schema>();
for (int j = 0; j < i; j++)
recs.add(Schema.createRecord(""+(char)('A'+j), null, null, false));
for (Schema s : recs) {
Schema union = Schema.createUnion(recs);
Field f = new Field("x", union, null, null);
List<Field> fields = new ArrayList<Field>();
fields.add(f);
s.setFields(fields);
}
// check that equals and hashcode are correct and complete in a
// reasonable amount of time
for (Schema s1 : recs) {
Schema s2 = Schema.parse(s1.toString());
assertEquals(s1.hashCode(), s2.hashCode());
assertEquals(s1, s2);
}
}
}
@Test
public void testLisp() throws Exception {
check(LISP_SCHEMA, false);
}
@Test
public void testUnion() throws Exception {
check("[\"string\", \"long\"]", false);
checkDefault("[\"double\", \"long\"]", "1.1", new Double(1.1));
// test that erroneous default values cause errors
for (String type : new String[]
{"int", "long", "float", "double", "string", "bytes", "boolean"}) {
boolean error = false;
try {
checkDefault("[\""+type+"\", \"null\"]", "null", 0);
} catch (AvroTypeException e) {
error = true;
}
assertTrue(error);
error = false;
try {
checkDefault("[\"null\", \""+type+"\"]", "0", null);
} catch (AvroTypeException e) {
error = true;
}
assertTrue(error);
}
// check union json
String record = "{\"type\":\"record\",\"name\":\"Foo\",\"fields\":[]}";
String fixed = "{\"type\":\"fixed\",\"name\":\"Bar\",\"size\": 1}";
String enu = "{\"type\":\"enum\",\"name\":\"Baz\",\"symbols\": [\"X\"]}";
Schema union = Schema.parse("[\"null\",\"string\","
+record+","+ enu+","+fixed+"]");
checkJson(union, null, "null");
checkJson(union, new Utf8("foo"), "{\"string\":\"foo\"}");
checkJson(union,
new GenericData.Record(Schema.parse(record)),
"{\"Foo\":{}}");
checkJson(union,
new GenericData.Fixed(Schema.parse(fixed), new byte[]{(byte)'a'}),
"{\"Bar\":\"a\"}");
checkJson(union,
new GenericData.EnumSymbol(Schema.parse(enu), "X"),
"{\"Baz\":\"X\"}");
}
@Test
public void testComplexUnions() throws Exception {
// one of each unnamed type and two of named types
String partial = "[\"int\", \"long\", \"float\", \"double\", \"boolean\", \"bytes\"," +
" \"string\", {\"type\":\"array\", \"items\": \"long\"}," +
" {\"type\":\"map\", \"values\":\"long\"}";
String namedTypes = ", {\"type\":\"record\",\"name\":\"Foo\",\"fields\":[]}," +
" {\"type\":\"fixed\",\"name\":\"Bar\",\"size\": 1}," +
" {\"type\":\"enum\",\"name\":\"Baz\",\"symbols\": [\"X\"]}";
String namedTypes2 = ", {\"type\":\"record\",\"name\":\"Foo2\",\"fields\":[]}," +
" {\"type\":\"fixed\",\"name\":\"Bar2\",\"size\": 1}," +
" {\"type\":\"enum\",\"name\":\"Baz2\",\"symbols\": [\"X\"]}";
check(partial + namedTypes + "]", false);
check(partial + namedTypes + namedTypes2 + "]", false);
checkParseError(partial + namedTypes + namedTypes + "]");
// fail with two branches of the same unnamed type
checkUnionError(new Schema[] {Schema.create(Type.INT), Schema.create(Type.INT)});
checkUnionError(new Schema[] {Schema.create(Type.LONG), Schema.create(Type.LONG)});
checkUnionError(new Schema[] {Schema.create(Type.FLOAT), Schema.create(Type.FLOAT)});
checkUnionError(new Schema[] {Schema.create(Type.DOUBLE), Schema.create(Type.DOUBLE)});
checkUnionError(new Schema[] {Schema.create(Type.BOOLEAN), Schema.create(Type.BOOLEAN)});
checkUnionError(new Schema[] {Schema.create(Type.BYTES), Schema.create(Type.BYTES)});
checkUnionError(new Schema[] {Schema.create(Type.STRING), Schema.create(Type.STRING)});
checkUnionError(new Schema[] {Schema.createArray(Schema.create(Type.INT)),
Schema.createArray(Schema.create(Type.INT))});
checkUnionError(new Schema[] {Schema.createMap(Schema.create(Type.INT)),
Schema.createMap(Schema.create(Type.INT))});
List<String> symbols = new ArrayList<String>();
symbols.add("NOTHING");
// succeed with two branches of the same named type, if different names
Schema u;
u = buildUnion(new Schema[] {
Schema.parse("{\"type\":\"record\",\"name\":\"x.A\",\"fields\":[]}"),
Schema.parse("{\"type\":\"record\",\"name\":\"y.A\",\"fields\":[]}")});
check(u.toString(), false);
u = buildUnion(new Schema[] {
Schema.parse
("{\"type\":\"enum\",\"name\":\"x.A\",\"symbols\":[\"X\"]}"),
Schema.parse
("{\"type\":\"enum\",\"name\":\"y.A\",\"symbols\":[\"Y\"]}")});
check(u.toString(), false);
u = buildUnion(new Schema[] {
Schema.parse("{\"type\":\"fixed\",\"name\":\"x.A\",\"size\":4}"),
Schema.parse("{\"type\":\"fixed\",\"name\":\"y.A\",\"size\":8}")});
check(u.toString(), false);
// fail with two branches of the same named type, but same names
checkUnionError(new Schema[] {Schema.createRecord("Foo", null, "org.test", false),
Schema.createRecord("Foo", null, "org.test", false)});
checkUnionError(new Schema[] {Schema.createEnum("Bar", null, "org.test", symbols),
Schema.createEnum("Bar", null, "org.test", symbols)});
checkUnionError(new Schema[] {Schema.createFixed("Baz", null, "org.test", 2),
Schema.createFixed("Baz", null, "org.test", 1)});
Schema union = buildUnion(new Schema[] {Schema.create(Type.INT)});
// fail if creating a union of a union
checkUnionError(new Schema[] {union});
}
@Test
public void testComplexProp() throws Exception {
String json = "{\"type\":\"null\", \"foo\": [0]}";
Schema s = Schema.parse(json);
assertEquals(null, s.getProp("foo"));
}
@Test
public void testParseInputStream() throws IOException {
Schema s = Schema.parse(
new ByteArrayInputStream("\"boolean\"".getBytes("UTF-8")));
assertEquals(Schema.parse("\"boolean\""), s);
}
@Test
public void testNamespaceScope() throws Exception {
String z = "{\"type\":\"record\",\"name\":\"Z\",\"fields\":[]}";
String y = "{\"type\":\"record\",\"name\":\"q.Y\",\"fields\":["
+"{\"name\":\"f\",\"type\":"+z+"}]}";
String x = "{\"type\":\"record\",\"name\":\"p.X\",\"fields\":["
+"{\"name\":\"f\",\"type\":"+y+"},"
+"{\"name\":\"g\",\"type\":"+z+"}"
+"]}";
Schema xs = Schema.parse(x);
Schema ys = xs.getField("f").schema();
assertEquals("p.Z", xs.getField("g").schema().getFullName());
assertEquals("q.Z", ys.getField("f").schema().getFullName());
}
@Test
public void testNamespaceNesting() throws Exception {
String y = "{\"type\":\"record\",\"name\":\"y.Y\",\"fields\":["
+"{\"name\":\"f\",\"type\":\"x.X\"}]}";
String x = "{\"type\":\"record\",\"name\":\"x.X\",\"fields\":["
+"{\"name\":\"f\",\"type\":"+y+"}"
+"]}";
Schema xs = Schema.parse(x);
System.out.println(xs);
assertEquals(xs, Schema.parse(xs.toString()));
}
@Test
public void testNullPointer() throws Exception {
String recordJson = "{\"type\":\"record\", \"name\":\"Test\", \"fields\":"
+"[{\"name\":\"x\", \"type\":\"string\"}]}";
Schema schema = Schema.parse(recordJson);
GenericData.Record record = new GenericData.Record(schema);
try {
checkBinary(schema, record,
new GenericDatumWriter<Object>(),
new GenericDatumReader<Object>());
} catch (NullPointerException e) {
assertEquals("null of string in field x of Test", e.getMessage());
}
}
private static void checkParseError(String json) {
try {
Schema.parse(json);
} catch (SchemaParseException e) {
return;
}
fail("Should not have parsed: "+json);
}
private static void checkUnionError(Schema[] branches) {
List<Schema> branchList = Arrays.asList(branches);
try {
Schema.createUnion(branchList);
fail("Union should not have constructed from: " + branchList);
} catch (AvroRuntimeException are) {
return;
}
}
private static Schema buildUnion(Schema[] branches) {
List<Schema> branchList = Arrays.asList(branches);
return Schema.createUnion(branchList);
}
/**
* Makes sure that "doc" tags are transcribed in the schemas.
* Note that there are docs both for fields and for the records
* themselves.
*/
@Test
public void testDocs() {
Schema schema = Schema.parse(SCHEMA_WITH_DOC_TAGS);
assertEquals("This is not a world record.", schema.getDoc());
assertEquals("Inner Fixed", schema.getField("inner_fixed").doc());
assertEquals("Very Inner Fixed", schema.getField("inner_fixed").schema().getDoc());
assertEquals("Inner String", schema.getField("inner_string").doc());
assertEquals("Inner Enum", schema.getField("inner_enum").doc());
assertEquals("Very Inner Enum", schema.getField("inner_enum").schema().getDoc());
assertEquals("Inner Union", schema.getField("inner_union").doc());
}
@Test
public void testFieldDocs() {
String schemaStr = "{\"name\": \"Rec\",\"type\": \"record\",\"fields\" : ["+
"{\"name\": \"f\", \"type\": \"int\", \"doc\": \"test\"}]}";
// check field doc is parsed correctly
Schema schema = Schema.parse(schemaStr);
assertEquals("test", schema.getField("f").doc());
// check print/read cycle preserves field doc
schema = Schema.parse(schema.toString());
assertEquals("test", schema.getField("f").doc());
}
@Test
public void testAliases() throws Exception {
String t1 = "{\"type\":\"record\",\"name\":\"a.b\",\"fields\":["
+"{\"name\":\"f\",\"type\":\"long\"},"
+"{\"name\":\"h\",\"type\":\"int\"}]}";
String t2 = "{\"type\":\"record\",\"name\":\"x.y\",\"aliases\":[\"a.b\"],"
+"\"fields\":[{\"name\":\"g\",\"type\":\"long\",\"aliases\":[\"f\"]},"
+"{\"name\":\"h\",\"type\":\"int\"}]}";
Schema s1 = Schema.parse(t1);
Schema s2 = Schema.parse(t2);
Schema s3 = Schema.applyAliases(s1,s2);
assertFalse(s2 == s3);
assertEquals(s2, s3);
t1 = "{\"type\":\"enum\",\"name\":\"a.b\","
+"\"symbols\":[\"x\"]}";
t2 = "{\"type\":\"enum\",\"name\":\"a.c\",\"aliases\":[\"b\"],"
+"\"symbols\":[\"x\"]}";
s1 = Schema.parse(t1);
s2 = Schema.parse(t2);
s3 = Schema.applyAliases(s1,s2);
assertFalse(s2 == s3);
assertEquals(s2, s3);
t1 = "{\"type\":\"fixed\",\"name\":\"a\","
+"\"size\": 5}";
t2 = "{\"type\":\"fixed\",\"name\":\"b\",\"aliases\":[\"a\"],"
+"\"size\": 5}";
s1 = Schema.parse(t1);
s2 = Schema.parse(t2);
s3 = Schema.applyAliases(s1,s2);
assertFalse(s2 == s3);
assertEquals(s2, s3);
}
private static void check(String schemaJson, String defaultJson,
Object defaultValue) throws Exception {
check(schemaJson, defaultJson, defaultValue, true);
}
private static void check(String schemaJson, String defaultJson,
Object defaultValue, boolean induce)
throws Exception {
check(schemaJson, induce);
checkDefault(schemaJson, defaultJson, defaultValue);
}
private static void check(String jsonSchema, boolean induce)
throws Exception {
Schema schema = Schema.parse(jsonSchema);
checkProp(schema);
for (Object datum : new RandomData(schema, COUNT)) {
if (induce) {
Schema induced = GenericData.get().induce(datum);
assertEquals("Induced schema does not match.", schema, induced);
}
assertTrue("Datum does not validate against schema "+datum,
GenericData.get().validate(schema, datum));
checkBinary(schema, datum,
new GenericDatumWriter<Object>(),
new GenericDatumReader<Object>());
checkDirectBinary(schema, datum,
new GenericDatumWriter<Object>(),
new GenericDatumReader<Object>());
checkBlockingBinary(schema, datum,
new GenericDatumWriter<Object>(),
new GenericDatumReader<Object>());
checkJson(schema, datum,
new GenericDatumWriter<Object>(),
new GenericDatumReader<Object>());
// Check that we can generate the code for every schema we see.
TestSpecificCompiler.assertCompiles(schema, false);
}
}
private static void checkProp(Schema s0) throws Exception {
if(s0.getType().equals(Schema.Type.UNION)) return; // unions have no props
assertEquals(null, s0.getProp("foo"));
Schema s1 = Schema.parse(s0.toString());
s1.addProp("foo", "bar");
assertEquals("bar", s1.getProp("foo"));
assertFalse(s0.equals(s1));
Schema s2 = Schema.parse(s1.toString());
assertEquals("bar", s2.getProp("foo"));
assertEquals(s1, s2);
assertFalse(s0.equals(s2));
}
public static void checkBinary(Schema schema, Object datum,
DatumWriter<Object> writer,
DatumReader<Object> reader)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writer.setSchema(schema);
Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
writer.write(datum, encoder);
encoder.flush();
byte[] data = out.toByteArray();
reader.setSchema(schema);
Object decoded =
reader.read(null, DecoderFactory.get().binaryDecoder(
data, null));
assertEquals("Decoded data does not match.", datum, decoded);
}
public static void checkDirectBinary(Schema schema, Object datum,
DatumWriter<Object> writer, DatumReader<Object> reader)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writer.setSchema(schema);
Encoder encoder = EncoderFactory.get().directBinaryEncoder(out, null);
writer.write(datum, encoder);
// no flush for direct
byte[] data = out.toByteArray();
reader.setSchema(schema);
Object decoded = reader.read(null, DecoderFactory.get()
.binaryDecoder(data, null));
assertEquals("Decoded data does not match.", datum, decoded);
}
public static void checkBlockingBinary(Schema schema, Object datum,
DatumWriter<Object> writer, DatumReader<Object> reader)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writer.setSchema(schema);
Encoder encoder = EncoderFactory.get().blockingBinaryEncoder(out, null);
writer.write(datum, encoder);
encoder.flush();
byte[] data = out.toByteArray();
reader.setSchema(schema);
Object decoded = reader.read(null, DecoderFactory.get()
.binaryDecoder(data, null));
assertEquals("Decoded data does not match.", datum, decoded);
}
private static void checkJson(Schema schema, Object datum,
DatumWriter<Object> writer,
DatumReader<Object> reader)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Encoder encoder = EncoderFactory.get().jsonEncoder(schema, out);
writer.setSchema(schema);
writer.write(datum, encoder);
writer.write(datum, encoder);
encoder.flush();
byte[] data = out.toByteArray();
reader.setSchema(schema);
Decoder decoder = DecoderFactory.get().jsonDecoder(schema,
new ByteArrayInputStream(data));
Object decoded = reader.read(null, decoder);
assertEquals("Decoded data does not match.", datum, decoded);
decoded = reader.read(decoded, decoder);
assertEquals("Decoded data does not match.", datum, decoded);
}
private static void checkJson(Schema schema, Object datum,
String json) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Encoder encoder = EncoderFactory.get().jsonEncoder(schema, out);
DatumWriter<Object> writer = new GenericDatumWriter<Object>();
writer.setSchema(schema);
writer.write(datum, encoder);
encoder.flush();
byte[] data = out.toByteArray();
String encoded = new String(data, "UTF-8");
assertEquals("Encoded data does not match.", json, encoded);
DatumReader<Object> reader = new GenericDatumReader<Object>();
reader.setSchema(schema);
Object decoded = reader.read(null, DecoderFactory.get()
.jsonDecoder(schema, new ByteArrayInputStream(data)));
assertEquals("Decoded data does not match.", datum, decoded);
}
private static final Schema ACTUAL = // an empty record schema
Schema.parse("{\"type\":\"record\", \"name\":\"Foo\", \"fields\":[]}");
private static void checkDefault(String schemaJson, String defaultJson,
Object defaultValue) throws Exception {
String recordJson =
"{\"type\":\"record\", \"name\":\"Foo\", \"fields\":[{\"name\":\"f\", "
+"\"type\":"+schemaJson+", "
+"\"default\":"+defaultJson+"}]}";
Schema expected = Schema.parse(recordJson);
DatumReader<Object> in = new GenericDatumReader<Object>(ACTUAL, expected);
GenericData.Record record = (GenericData.Record)
in.read(null, DecoderFactory.get().binaryDecoder(
new byte[0], null));
assertEquals("Wrong default.", defaultValue, record.get("f"));
assertEquals("Wrong toString", expected, Schema.parse(expected.toString()));
}
@Test(expected=AvroTypeException.class)
public void testNoDefaultField() throws Exception {
Schema expected =
Schema.parse("{\"type\":\"record\", \"name\":\"Foo\", \"fields\":"+
"[{\"name\":\"f\", \"type\": \"string\"}]}");
DatumReader<Object> in = new GenericDatumReader<Object>(ACTUAL, expected);
in.read(null, DecoderFactory.get().binaryDecoder(
new ByteArrayInputStream(new byte[0]), null));
}
@Test
public void testEnumMismatch() throws Exception {
Schema actual = Schema.parse
("{\"type\":\"enum\",\"name\":\"E\",\"symbols\":[\"X\",\"Y\"]}");
Schema expected = Schema.parse
("{\"type\":\"enum\",\"name\":\"E\",\"symbols\":[\"Y\",\"Z\"]}");
ByteArrayOutputStream out = new ByteArrayOutputStream();
DatumWriter<Object> writer = new GenericDatumWriter<Object>(actual);
Encoder encoder = EncoderFactory.get().directBinaryEncoder(out, null);
writer.write(new GenericData.EnumSymbol(actual, "Y"), encoder);
writer.write(new GenericData.EnumSymbol(actual, "X"), encoder);
encoder.flush();
byte[] data = out.toByteArray();
Decoder decoder = DecoderFactory.get().binaryDecoder(
data, null);
DatumReader<String> in = new GenericDatumReader<String>(actual, expected);
assertEquals("Wrong value", new GenericData.EnumSymbol(expected, "Y"),
in.read(null, decoder));
try {
in.read(null, decoder);
fail("Should have thrown exception.");
} catch (AvroTypeException e) {
// expected
}
}
@Test(expected=AvroTypeException.class)
public void testRecordWithPrimitiveName() {
Schema.parse("{\"type\":\"record\", \"name\":\"string\", \"fields\": []}");
}
@Test(expected=AvroTypeException.class)
public void testEnumWithPrimitiveName() {
Schema.parse("{\"type\":\"enum\", \"name\":\"null\", \"symbols\": [\"A\"]}");
}
private static Schema enumSchema() {
return Schema.parse("{ \"type\": \"enum\", \"name\": \"e\", "
+ "\"symbols\": [\"a\", \"b\"]}");
}
@Test(expected=AvroRuntimeException.class)
public void testImmutability1() {
Schema s = enumSchema();
s.addProp("p1", "1");
s.addProp("p1", "2");
}
@Test(expected=AvroRuntimeException.class)
public void testImmutability2() {
Schema s = enumSchema();
s.addProp("p1", null);
}
private static List<String> lockedArrayList() {
return new Schema.LockableArrayList<String>(Arrays.asList(new String[] {
"a", "b", "c" })).lock();
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList1() {
lockedArrayList().add("p");
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList2() {
lockedArrayList().remove("a");
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList3() {
lockedArrayList().addAll(Arrays.asList(new String[] { "p" }));
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList4() {
lockedArrayList().addAll(0,
Arrays.asList(new String[] { "p" }));
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList5() {
lockedArrayList().
removeAll(Arrays.asList(new String[] { "a" }));
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList6() {
lockedArrayList().
retainAll(Arrays.asList(new String[] { "a" }));
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList7() {
lockedArrayList().clear();
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList8() {
lockedArrayList().iterator().remove();
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList9() {
Iterator<String> it = lockedArrayList().iterator();
it.next();
it.remove();
}
@Test(expected=IllegalStateException.class)
public void testLockedArrayList10() {
lockedArrayList().remove(1);
}
}