From f6843e5de313dfdbefe03926ef5f959ddc2aad92 Mon Sep 17 00:00:00 2001 From: Uttam Kumar Date: Wed, 6 Sep 2023 21:33:13 -0700 Subject: [PATCH] Added unit test to compile add generated class. --- avro-builder/tests/tests-allavro/build.gradle | 2 + .../avroutil1/builder/SpecificRecordTest.java | 27 +++ .../SpecificRecordClassGeneratorTest.java | 4 +- .../avroutil1/testutil/CompilerHelper.java | 2 + .../testcommon/CommonCompilerHelper.java | 183 ++++++++++++++++++ 5 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 test-common/src/main/java/com/linkedin/avroutil1/testcommon/CommonCompilerHelper.java diff --git a/avro-builder/tests/tests-allavro/build.gradle b/avro-builder/tests/tests-allavro/build.gradle index 223160ada..bd984daf0 100644 --- a/avro-builder/tests/tests-allavro/build.gradle +++ b/avro-builder/tests/tests-allavro/build.gradle @@ -97,6 +97,8 @@ dependencies { exclude group: "org.slf4j" } + testImplementation project(":test-common") + avro15 "org.apache.avro:avro:1.5.4" avro15 "org.apache.avro:avro-compiler:1.5.4" diff --git a/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java b/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java index 808df3e86..56859ba18 100644 --- a/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java +++ b/avro-builder/tests/tests-allavro/src/test/java/com/linkedin/avroutil1/builder/SpecificRecordTest.java @@ -11,10 +11,14 @@ import com.linkedin.avroutil1.compatibility.RandomRecordGenerator; import com.linkedin.avroutil1.compatibility.RecordGenerationConfig; import com.linkedin.avroutil1.compatibility.StringConverterUtil; +import com.linkedin.avroutil1.testcommon.CommonCompilerHelper; +import java.io.File; +import java.io.FilenameFilter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1829,6 +1833,29 @@ public void testNewBuilder() throws Exception { compareIndexedRecords(instance, builder.build()); } + @DataProvider + private Object[][] testGeneratedCompilationProvider(){ + String projectPath = System.getProperty("user.dir"); + List list = Arrays.stream(new File(projectPath + "/../").list(new FilenameFilter() { + @Override + public boolean accept(File current, String name) { + return name.matches("codegen-[\\w\\-]+") && new File(current, name).isDirectory(); + } + })).map(dir -> projectPath + "/../" + dir + "/build/generated/sources/avro").collect(Collectors.toList()); + + Object[][] res = new Object[list.size()][1]; + + for(int i = 0; i< list.size(); i++) { + res[i][0] = list.get(i); + } + return res; + } + + @Test(dataProvider = "testGeneratedCompilationProvider") + public void testGeneratedCompilation(String genPath) throws Exception { + CommonCompilerHelper.assertCompiles(Paths.get(genPath)); + } + @BeforeClass public void setup() { System.setProperty("org.apache.avro.specific.use_custom_coders", "true"); diff --git a/avro-codegen/src/test/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGeneratorTest.java b/avro-codegen/src/test/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGeneratorTest.java index cf8ed0ccf..496094b79 100644 --- a/avro-codegen/src/test/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGeneratorTest.java +++ b/avro-codegen/src/test/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGeneratorTest.java @@ -11,8 +11,8 @@ import com.linkedin.avroutil1.model.AvroRecordSchema; import com.linkedin.avroutil1.parser.avsc.AvscParseResult; import com.linkedin.avroutil1.parser.avsc.AvscParser; +import com.linkedin.avroutil1.testcommon.CommonCompilerHelper; import com.linkedin.avroutil1.testcommon.TestUtil; -import com.linkedin.avroutil1.testutil.CompilerHelper; import java.io.IOException; import javax.tools.JavaFileObject; import org.testng.Assert; @@ -56,7 +56,7 @@ public void testSchema(String path, boolean checkCompiles) throws Exception { generator.generateSpecificClass(schema, SpecificRecordGenerationConfig.BROAD_COMPATIBILITY); if (checkCompiles) { - CompilerHelper.assertCompiles(javaFileObject); + CommonCompilerHelper.assertCompiles(javaFileObject); } } diff --git a/avro-codegen/src/test/java/com/linkedin/avroutil1/testutil/CompilerHelper.java b/avro-codegen/src/test/java/com/linkedin/avroutil1/testutil/CompilerHelper.java index 67ec243b2..48c7010b7 100644 --- a/avro-codegen/src/test/java/com/linkedin/avroutil1/testutil/CompilerHelper.java +++ b/avro-codegen/src/test/java/com/linkedin/avroutil1/testutil/CompilerHelper.java @@ -37,7 +37,9 @@ /** * utility class for dealing with the java compiler in unit tests + * @deprecated Use test-common -> CommonCompilerHelper */ +@Deprecated public class CompilerHelper { public static void assertCompiles(JavaFileObject sourceFile) throws Exception { diff --git a/test-common/src/main/java/com/linkedin/avroutil1/testcommon/CommonCompilerHelper.java b/test-common/src/main/java/com/linkedin/avroutil1/testcommon/CommonCompilerHelper.java new file mode 100644 index 000000000..9b63878fd --- /dev/null +++ b/test-common/src/main/java/com/linkedin/avroutil1/testcommon/CommonCompilerHelper.java @@ -0,0 +1,183 @@ +/* + * Copyright 2023 LinkedIn Corp. + * Licensed under the BSD 2-Clause License (the "License"). + * See License in the project root for license information. + */ + +package com.linkedin.avroutil1.testcommon; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import org.apache.commons.io.IOUtils; +import org.testng.Assert; + + +/** + * utility class for dealing with the java compiler in unit tests + */ +public class CommonCompilerHelper { + + public static void assertCompiles(JavaFileObject sourceFile) throws Exception { + List fileObjects = Arrays.asList(sourceFile); + assertCompiles(fileObjects); + } + + public static void assertCompiles(Path sourceRoot) throws Exception { + List fileObjects = listSourceFiles(sourceRoot); + assertCompiles(fileObjects); + } + + public static void assertCompiles(List fileObjects) throws Exception { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + ClassFileManager manager = new ClassFileManager(compiler.getStandardFileManager(null, null, null)); + CompilationListener listener = new CompilationListener(); + JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, manager, listener, null, null, fileObjects); + Boolean success = compilationTask.call(); + + if (!Boolean.TRUE.equals(success) || !listener.errors.isEmpty()) { + Assert.fail("failed to compile code: " + listener.errors); + } + } + + private static List listSourceFiles(Path root) throws IOException { + List fileObjects = new ArrayList<>(); + //noinspection Convert2Diamond because java 8 isnt smart enough to figure this out + Files.walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (attrs.isRegularFile()) { + fileObjects.add(new JavaSourceFile(file)); + } + return FileVisitResult.CONTINUE; + } + }); + return fileObjects; + } + + private static String summarize(Diagnostic diagnostic) { + StringBuilder sb = new StringBuilder(); + sb.append(diagnostic.getKind()).append(": "); + JavaFileObject sourceObject = diagnostic.getSource(); + if (sourceObject != null) { + sb.append("file ").append(sourceObject.toString()).append(" "); + } + String message = diagnostic.getMessage(Locale.ROOT); + if (message != null && !message.isEmpty()) { + sb.append(message).append(" "); + } + long line = diagnostic.getLineNumber(); + long column = diagnostic.getColumnNumber(); + if (line != -1 || column != -1) { + sb.append("at line ").append(line).append(" column ").append(column); + } + return sb.toString().trim(); + } + + private static class ClassFileManager extends ForwardingJavaFileManager { + private final Map classObjects = new ConcurrentHashMap<>(); + + ClassFileManager(StandardJavaFileManager m) { + super(m); + } + + @Override + public JavaFileObject getJavaFileForOutput( + Location location, + String className, + JavaFileObject.Kind kind, + FileObject sibling + ) throws IOException { + if (kind != JavaFileObject.Kind.CLASS) { + throw new UnsupportedOperationException("unhandled kind " + kind); + } + return classObjects.computeIfAbsent(className, s -> new JavaClassObject((JavaFileObject) sibling, className)); + } + + } + + private static class JavaSourceFile extends SimpleJavaFileObject { + private final Path file; + + JavaSourceFile(Path file) { + super(file.toUri(), Kind.SOURCE); + this.file = file; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return IOUtils.toString(file.toUri(), StandardCharsets.UTF_8); + } + + @Override + public String toString() { + return file.getFileName().toString(); + } + } + + private static class JavaClassObject extends SimpleJavaFileObject { + private final JavaFileObject sourceFile; + private final String fqcn; + private volatile ByteArrayOutputStream outputStream; + + public JavaClassObject(JavaFileObject sourceFile, String fqcn) { + super(URI.create("mem:///" + fqcn.replace('.', '/')), Kind.CLASS); + this.sourceFile = sourceFile; + this.fqcn = fqcn; + } + + @Override + public synchronized OutputStream openOutputStream() { + if (outputStream == null) { + outputStream = new ByteArrayOutputStream(); + } + return outputStream; + } + } + + private static class CompilationListener implements DiagnosticListener { + private final List warnings = new ArrayList<>(); + private final List errors = new ArrayList<>(); + + @Override + public void report(Diagnostic diagnostic) { + Diagnostic.Kind kind = diagnostic.getKind(); + String desc = summarize(diagnostic); + switch (kind) { + case NOTE: + case WARNING: + case MANDATORY_WARNING: + warnings.add(desc); + break; + case ERROR: + case OTHER: + errors.add(desc); + break; + default: + throw new IllegalStateException("unhandled " + kind); + } + } + } +}