/* * Copyright 2018 LinkedIn Corp. * Licensed under the BSD 2-Clause License (the "License"). * See License in the project root for license information. */ package com.linkedin.avro.compatibility; import com.acme.generatedbylatest.EnumType; import com.acme.generatedbylatest.Event; import com.acme.generatedbylatest.Guid; import com.acme.generatedbylatest.Header; import com.linkedin.avro.TestUtil; import com.linkedin.avro.legacy.LegacyAvroSchema; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import org.apache.avro.Schema; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericRecord; import org.apache.avro.io.BinaryDecoder; import org.apache.avro.io.BinaryEncoder; import org.apache.avro.util.Utf8; import org.testng.Assert; import org.testng.SkipException; import org.testng.annotations.Test; public class AvroCompatibilityHelperTest { private Schema _schema = Schema.parse( "{\n" + " \"type\": \"record\",\n" + " \"name\": \"Event\",\n" + " \"namespace\": \"com.acme\",\n" + " \"fields\" : [\n" + " {\"name\":\"header\",\n" + " \"type\":{\n" + " \"name\": \"Header\",\n" + " \"type\": \"record\",\n" + " \"namespace\": \"com.acme\",\n" + " \"fields\" : [\n" + " {\"name\":\"intField\", \"type\":\"int\"},\n" + " {\"name\":\"guid\", \n" + " \"type\":{\n" + " \"name\": \"Guid\",\n" + " \"type\":\"fixed\",\n" + " \"size\":16\n" + " }\n" + " }\n" + " ]\n" + " }\n" + " },\n" + " {\"name\":\"enumField\", \n" + " \"type\":{\n" + " \"type\":\"enum\", \n" + " \"symbols\":[\"A\", \"B\", \"C\"], \n" + " \"name\":\"EnumType\"\n" + " }\n" + " },\n" + " {\"name\":\"strField\", \"type\":\"string\", \"default\":\"str\"}\n" + " ]\n" + "}" ); @Test public void testFixedField() throws Exception { Schema fixedTypeSchema = Schema.parse("{\"name\": \"UUID\", \"type\":\"fixed\", \"size\":16}"); GenericData.Fixed fixed = AvroCompatibilityHelper.newFixedField(fixedTypeSchema); Assert.assertNotNull(fixed); } @Test public void testGetDefaultValue() { Object defaultValue = AvroCompatibilityHelper.getDefaultValue(_schema.getField("strField")); Assert.assertEquals(defaultValue, new Utf8("str")); } @Test public void testBinaryEncoder() throws Exception { ByteArrayOutputStream os = new ByteArrayOutputStream(); BinaryEncoder binaryEncoder = AvroCompatibilityHelper.newBinaryEncoder(os); Assert.assertNotNull(binaryEncoder); } @Test public void testBinaryDecoder() { ByteArrayInputStream is = new ByteArrayInputStream(new byte[0]); BinaryDecoder binaryDecoder = AvroCompatibilityHelper.newBinaryDecoder(is); Assert.assertNotNull(binaryDecoder); } @Test public void testCompilerCurrent() throws Exception { Collection<AvroGeneratedSourceCode> compiled = AvroCompatibilityHelper.compile(Collections.singletonList(_schema), AvroCompatibilityHelper .getRuntimeAvroVersion()); Assert.assertEquals(4, compiled.size()); //Event, Header, EnumType and Guid assertCompiles(compiled); } @Test public void testCompilerCompatible() throws Exception { Collection<AvroGeneratedSourceCode> compiled = AvroCompatibilityHelper.compile(Collections.singletonList(_schema), AvroVersion.AVRO_1_4); Assert.assertEquals(4, compiled.size()); //Event, Header, EnumType and Guid assertCompiles(compiled); } @Test public void test17Compatibility() throws Exception { //exercise code generated by helper under 1.8 + compatibility under whatever avro this build runs on //(1.4 at the time of this writing) Guid guid = new Guid(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); Header header = new Header(); header.intField = 42; header.guid = guid; Event event = new Event(header, EnumType.B, "some string"); } @Test public void testCompileLargeSchema() throws Exception { String schemaJson = TestUtil.load("VeryLong.avsc"); Schema schema = Schema.parse(schemaJson); Collection<AvroGeneratedSourceCode> compiled = AvroCompatibilityHelper.compile(Collections.singletonList(schema), AvroVersion.AVRO_1_4); Assert.assertEquals(1, compiled.size()); //no inner classes assertCompiles(compiled); } @Test public void testFixShortUnionBranches() throws Exception { if (!AvroCompatibilityHelper.getRuntimeAvroVersion().laterThan(AvroVersion.AVRO_1_4)) { throw new SkipException("test only valid under modern avro"); } String avsc = TestUtil.load("HasUnions.avsc"); String payload14 = TestUtil.load("HasUnions14.json"); String payload17 = TestUtil.load("HasUnions17.json"); LegacyAvroSchema schema = new LegacyAvroSchema("1234", avsc); GenericRecord deserialized14 = schema.deserializeJson(payload14); Assert.assertNotNull(deserialized14); Assert.assertNotNull(deserialized14.get("f1")); GenericRecord deserialized17 = schema.deserializeJson(payload17); Assert.assertNotNull(deserialized17); Assert.assertNotNull(deserialized17.get("f1")); } @Test public void testFixFullUnionBranches() throws Exception { if (AvroCompatibilityHelper.getRuntimeAvroVersion().laterThan(AvroVersion.AVRO_1_4)) { throw new SkipException("test only valid under avro 1.4 and earlier"); } String avsc = TestUtil.load("HasUnions.avsc"); String payload14 = TestUtil.load("HasUnions14.json"); String payload17 = TestUtil.load("HasUnions17.json"); LegacyAvroSchema schema = new LegacyAvroSchema("1234", avsc); GenericRecord deserialized14 = schema.deserializeJson(payload14); Assert.assertNotNull(deserialized14); Assert.assertNotNull(deserialized14.get("f1")); GenericRecord deserialized17 = schema.deserializeJson(payload17); Assert.assertNotNull(deserialized17); Assert.assertNotNull(deserialized17.get("f1")); } @Test public void testIsSpecificRecord() { GenericRecord genericRecord = new GenericData.Record(_schema); Assert.assertFalse(AvroCompatibilityHelper.isSpecificRecord(genericRecord)); Assert.assertTrue(AvroCompatibilityHelper.isGenericRecord(genericRecord)); } private void assertCompiles(Collection<AvroGeneratedSourceCode> generatedCode) throws Exception { //write out generated code into a source tree Path tempRootFolder = Files.createTempDirectory(null); File[] fileArray = new File[generatedCode.size()]; int i=0; for (AvroGeneratedSourceCode avroGeneratedSourceCode : generatedCode) { fileArray[i++] = avroGeneratedSourceCode.writeToDestination(tempRootFolder.toFile()); } //spin up a java compiler task, use current runtime classpath, point at source tree created above List<String> optionList = new ArrayList<>(); optionList.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path"))); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> javaFileObjects = fileManager.getJavaFileObjects(fileArray); StringWriter compilerOutput = new StringWriter(); JavaCompiler.CompilationTask task = compiler.getTask(compilerOutput, fileManager, null, optionList, null, javaFileObjects); compilerOutput.flush(); //compile, assert no errors Assert.assertTrue(task.call(), "compilation failed with " + compilerOutput.toString()); } }