summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2020-06-22 17:53:09 +0200
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2020-06-22 17:53:09 +0200
commit6914514f59b1f79f72f3a25597c3a3683f41d8f8 (patch)
tree579fb720d30a1742cd0ac3d7b57b27a040ac46fb
parentdb44411be03f7770456240ff11ed33a4cfe53725 (diff)
Add vespa-maven-plugin goal to generate test descriptor file
-rw-r--r--vespa-maven-plugin/pom.xml15
-rw-r--r--vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/GenerateTestDescriptorMojo.java62
-rw-r--r--vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/TestAnnotationAnalyzer.java74
3 files changed, 151 insertions, 0 deletions
diff --git a/vespa-maven-plugin/pom.xml b/vespa-maven-plugin/pom.xml
index 21c79d65ac9..0910f38d5e5 100644
--- a/vespa-maven-plugin/pom.xml
+++ b/vespa-maven-plugin/pom.xml
@@ -44,8 +44,23 @@
<artifactId>config-application-package</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>tenant-cd-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>yolean</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
</dependency>
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<Path> 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<String> systemTests = new ArrayList<>();
+ private final List<String> stagingTests = new ArrayList<>();
+ private final List<String> productionTests = new ArrayList<>();
+
+ List<String> systemTests() { return systemTests; }
+ List<String> stagingTests() { return stagingTests; }
+ List<String> 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;
+ }
+ }
+}