From 6914514f59b1f79f72f3a25597c3a3683f41d8f8 Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Mon, 22 Jun 2020 17:53:09 +0200 Subject: Add vespa-maven-plugin goal to generate test descriptor file --- .../hosted/plugin/GenerateTestDescriptorMojo.java | 62 ++++++++++++++++++ .../hosted/plugin/TestAnnotationAnalyzer.java | 74 ++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/GenerateTestDescriptorMojo.java create mode 100644 vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/TestAnnotationAnalyzer.java (limited to 'vespa-maven-plugin/src') diff --git a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/GenerateTestDescriptorMojo.java b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/GenerateTestDescriptorMojo.java new file mode 100644 index 00000000000..3e726354393 --- /dev/null +++ b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/GenerateTestDescriptorMojo.java @@ -0,0 +1,62 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.hosted.plugin; + +import ai.vespa.hosted.api.TestDescriptor; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +/** + * Generates a test descriptor file based on content of the compiled test classes + * + * @author bjorncs + */ +@Mojo(name = "generateTestDescriptor", threadSafe = true, defaultPhase = LifecyclePhase.PROCESS_TEST_CLASSES) +public class GenerateTestDescriptorMojo extends AbstractMojo { + + @Parameter(defaultValue = "${project}", readonly = true) + protected MavenProject project; + + @Override + public void execute() throws MojoExecutionException { + TestAnnotationAnalyzer analyzer = new TestAnnotationAnalyzer(); + analyzeTestClasses(analyzer); + TestDescriptor descriptor = TestDescriptor.from( + TestDescriptor.CURRENT_VERSION, + analyzer.systemTests(), + analyzer.stagingTests(), + analyzer.productionTests()); + writeDescriptorFile(descriptor); + } + + private void analyzeTestClasses(TestAnnotationAnalyzer analyzer) throws MojoExecutionException { + try (Stream files = Files.walk(testClassesDirectory())) { + files + .filter(f -> f.toString().endsWith(".class")) + .forEach(analyzer::analyzeClass); + } catch (Exception e) { + throw new MojoExecutionException("Failed to analyze test classes: " + e.getMessage(), e); + } + } + + private void writeDescriptorFile(TestDescriptor descriptor) throws MojoExecutionException { + try { + Path descriptorFile = testClassesDirectory().resolve(TestDescriptor.DEFAULT_FILENAME); + Files.createDirectories(descriptorFile.getParent()); + Files.write(descriptorFile, descriptor.toJson().getBytes()); + } catch (IOException e) { + throw new MojoExecutionException("Failed to write test descriptor file: " + e.getMessage(), e); + } + } + + private Path testClassesDirectory() { return Paths.get(project.getBuild().getTestOutputDirectory()); } +} diff --git a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/TestAnnotationAnalyzer.java b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/TestAnnotationAnalyzer.java new file mode 100644 index 00000000000..c45ef21bc31 --- /dev/null +++ b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/TestAnnotationAnalyzer.java @@ -0,0 +1,74 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.hosted.plugin; + + +import ai.vespa.hosted.cd.ProductionTest; +import ai.vespa.hosted.cd.StagingTest; +import ai.vespa.hosted.cd.SystemTest; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * Analyzes test classes and tracks all classes containing hosted Vespa test annotations ({@link ai.vespa.hosted.cd}). + * + * @author bjorncs + */ +class TestAnnotationAnalyzer { + + private final List systemTests = new ArrayList<>(); + private final List stagingTests = new ArrayList<>(); + private final List productionTests = new ArrayList<>(); + + List systemTests() { return systemTests; } + List stagingTests() { return stagingTests; } + List productionTests() { return productionTests; } + + void analyzeClass(Path classFile) { + try (InputStream in = Files.newInputStream(classFile)) { + new ClassReader(in).accept(new AsmClassVisitor(), ClassReader.SKIP_DEBUG); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private class AsmClassVisitor extends ClassVisitor { + + private String className; + + AsmClassVisitor() { super(Opcodes.ASM7); } + + @Override + public void visit( + int version, int access, String name, String signature, String superName, String[] interfaces) { + Type type = Type.getObjectType(name); + if (type.getSort() == Type.OBJECT) { + this.className = type.getClassName(); + super.visit(version, access, name, signature, superName, interfaces); + } + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + String annotationClassName = Type.getType(descriptor).getClassName(); + if (ProductionTest.class.getName().equals(annotationClassName)) { + productionTests.add(className); + } else if (StagingTest.class.getName().equals(annotationClassName)) { + stagingTests.add(className); + } else if (SystemTest.class.getName().equals(annotationClassName)) { + systemTests.add(className); + } + return null; + } + } +} -- cgit v1.2.3