Enable SFT using dependency features from test scope 44/106844/5
authorRuslan Kashapov <ruslan.kashapov@pantheon.tech>
Wed, 5 Jul 2023 07:47:47 +0000 (10:47 +0300)
committerRobert Varga <nite@hq.sk>
Fri, 1 Dec 2023 15:27:45 +0000 (15:27 +0000)
Test scope features are pre-installed into karaf. Due to
karaf requires both maven reference and feature names the
required feature artifact locations are resolved by
maven-dependency-plugin then processed on SFT execution.

JIRA: ODLPARENT-257
Change-Id: Iac98cf72767f0e7473e58a153b055c59a38f0891
Signed-off-by: Ruslan Kashapov <ruslan.kashapov@pantheon.tech>
abstract-feature-parent/pom.xml
features-test/src/main/java/org/opendaylight/odlparent/featuretest/DependencyUtil.java [new file with mode: 0644]
features-test/src/main/java/org/opendaylight/odlparent/featuretest/SingleFeatureTest.java
features-test/src/test/java/org/opendaylight/odlparent/featuretest/DependencyUtilTest.java [new file with mode: 0644]

index 428ee4ef45fd615fa26174770d8cd2d375fc53d3..aa0408e9366a2876f8d51f12cfdba2bb8a8d3e65 100644 (file)
                 <groupId>org.apache.servicemix.tooling</groupId>
                 <artifactId>depends-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <!-- Lists feature dependencies defined in test scope to be pre-installed in karaf when SFT is executed.
+                     Resolved dependencies also include absolute path to artifacts within local repository so
+                     feature names (required for feature:install) can be retrieved. -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <!-- execute before test phase -->
+                        <phase>test-compile</phase>
+                        <goals>
+                            <goal>list</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <skip>${skip.karaf.featureTest}</skip>
+                    <includeClassifiers>features</includeClassifiers>
+                    <includeTypes>xml</includeTypes>
+                    <excludeScope>compile</excludeScope>
+                    <!-- exclude SFT's own dependency features -->
+                    <excludeGroupIds>org.apache.karaf.features</excludeGroupIds>
+                    <outputAbsoluteArtifactFilename>true</outputAbsoluteArtifactFilename>
+                    <outputFile>${project.build.directory}/test-features</outputFile>
+                </configuration>
+            </plugin>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
                     <systemPropertyVariables>
                         <!-- Use the same repository for Pax Exam as is used for Maven -->
                         <org.ops4j.pax.url.mvn.localRepository>${settings.localRepository}</org.ops4j.pax.url.mvn.localRepository>
+                        <!-- Test scope features resolved by maven-dependency-plugin (above) -->
+                        <featureTest.dependencies.list>${project.build.directory}/test-features</featureTest.dependencies.list>
                     </systemPropertyVariables>
 
                     <!-- Disable argLine if present, but pass it to SFT -->
diff --git a/features-test/src/main/java/org/opendaylight/odlparent/featuretest/DependencyUtil.java b/features-test/src/main/java/org/opendaylight/odlparent/featuretest/DependencyUtil.java
new file mode 100644 (file)
index 0000000..fc01dac
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.odlparent.featuretest;
+
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.Collection;
+import java.util.List;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.eclipse.jdt.annotation.NonNull;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.karaf.options.KarafDistributionOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class responsible for maven dependencies processing. Functionality relays on dependencies list populated
+ * by {@code maven-dependency-plugin} on execution prior to SFT's {@code test} phase.
+ */
+final class DependencyUtil {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DependencyUtil.class);
+    private static final String TEST_DEPENDENCIES_LIST_PROP = "featureTest.dependencies.list";
+    private static final String FEATURES = "features";
+    private static final String XML = "xml";
+    private static final String TEST = "test";
+
+    private DependencyUtil() {
+        // utility class
+    }
+
+    /**
+     * Retrieves a list of maven artefacts classified as "features" defined in "test" scope for current project.
+     *
+     * <p>Path to dependencies list file is taken from system property variable {@value TEST_DEPENDENCIES_LIST_PROP}.
+     *
+     * @return features as PAX conditional composite option
+     */
+    static @NonNull Option testFeatures() {
+        return testFeatures(new File(System.getProperty(TEST_DEPENDENCIES_LIST_PROP, "")));
+    }
+
+    /**
+     * Retrieves a list of maven artefacts classified as "features" defined in "test" scope for current project.
+     *
+     * @param listFile the file containing dependencies list
+     * @return features as PAX conditional composite option
+     */
+    static @NonNull Option testFeatures(final File listFile) {
+        final var options = loadDependencyDescriptors(listFile).stream()
+            .filter(depDesc -> depDesc.isFeature() && depDesc.isTestScope()
+                && depDesc.featureNames().length > 0)
+            .map(DependencyUtil::toFeatureOption).toArray(Option[]::new);
+        return CoreOptions.when(options.length > 0).useOptions(options);
+    }
+
+    private static @NonNull Option toFeatureOption(final DependencyDescriptor depDesc) {
+        final var mavenRef = CoreOptions.maven().groupId(depDesc.groupId).artifactId(depDesc.artifactId)
+            .version(depDesc.version).classifier(FEATURES).type(XML);
+        return KarafDistributionOption.features(mavenRef, depDesc.featureNames);
+    }
+
+    private static @NonNull Collection<DependencyDescriptor> loadDependencyDescriptors(final File listFile) {
+        if (!listFile.exists()) {
+            return List.of();
+        }
+        final var listBuilder = new ImmutableList.Builder<DependencyDescriptor>();
+        try {
+            // resolved dependencies are listed in following format
+            // groupId:artifactId:type:classifier:version:scope:absoluteFilePath
+            for (var line : Files.readAllLines(listFile.toPath(), Charset.defaultCharset())) {
+                final var parts = line.trim().split(":");
+                if (parts.length < 7) {
+                    continue;
+                }
+                final var groupId = parts[0];
+                final var artifactId = parts[1];
+                final var isFeature = XML.equals(parts[2]) && FEATURES.equals(parts[3]);
+                final var version = parts[4];
+                final var isTestScope = TEST.equals(parts[5]);
+                final var featureNames = isFeature ? getFeatureNames(new File(parts[6])) : new String[0];
+                listBuilder.add(
+                    new DependencyDescriptor(groupId, artifactId, version, isFeature, isTestScope, featureNames));
+            }
+        } catch (IOException e) {
+            LOG.warn("Error reading dependencies list from {}", listFile, e);
+        }
+        return listBuilder.build();
+    }
+
+    private static @NonNull String[] getFeatureNames(final File featureFile) {
+        if (featureFile.exists()) {
+            try (var inputStream = new FileInputStream(featureFile)) {
+                final Features feature = JaxbUtil.unmarshal(featureFile.toURI().toString(), inputStream, false);
+                return feature.getFeature().stream().map(Feature::getName).toArray(String[]::new);
+            } catch (IOException e) {
+                LOG.warn("Error reading features from {}", featureFile, e);
+            }
+        }
+        return new String[0];
+    }
+
+    private record DependencyDescriptor(String groupId, String artifactId, String version,
+        boolean isFeature, boolean isTestScope, String[] featureNames) {
+    }
+}
index fbafd6bd55a5bf98ec10b0f11a10b1b761196e3b..dedb9ae436b9b6f18d35a34dfc85de925932c674 100644 (file)
@@ -14,6 +14,7 @@ import static org.junit.Assert.fail;
 import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_FEATURETEST_FEATURENAME_PROP;
 import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_FEATURETEST_FEATUREVERSION_PROP;
 import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_FEATURETEST_URI_PROP;
+import static org.opendaylight.odlparent.featuretest.DependencyUtil.testFeatures;
 import static org.ops4j.pax.exam.CoreOptions.bootDelegationPackages;
 import static org.ops4j.pax.exam.CoreOptions.maven;
 import static org.ops4j.pax.exam.CoreOptions.propagateSystemProperty;
@@ -227,6 +228,9 @@ public class SingleFeatureTest {
             features(maven().groupId("org.apache.karaf.features").artifactId("standard").type("xml")
                 .classifier("features").versionAsInProject(), "scr"),
 
+            // Install features optionally defined in test scope
+            testFeatures(),
+
             // Enable JaCoCo, if present
             jacocoOption(),
         };
diff --git a/features-test/src/test/java/org/opendaylight/odlparent/featuretest/DependencyUtilTest.java b/features-test/src/test/java/org/opendaylight/odlparent/featuretest/DependencyUtilTest.java
new file mode 100644 (file)
index 0000000..bf9f680
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.odlparent.featuretest;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.karaf.options.KarafFeaturesOption;
+import org.ops4j.pax.exam.options.OptionalCompositeOption;
+
+class DependencyUtilTest {
+
+    @TempDir
+    File dir;
+
+    @Test
+    void testFeatures() throws IOException {
+        final var listFile = new File(dir, "test-dependencies");
+        final var artifactFile = new File(dir, "features-artifact.xml");
+        writeFile(artifactFile, """
+            <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+            <features xmlns="http://karaf.apache.org/xmlns/features/v1.6.0" name="odl-test">
+                <feature name="test-feature-name" version="1.2.3-SNAPSHOT">
+                    <bundle>mvn:some.group/some-artifact/0.0.1</bundle>
+                </feature>
+            </features>
+            """);
+        writeFile(listFile, String.format("""
+            The following files have been resolved:
+                org.opendaylight.odlparent:odl-test:xml:features:1.2.3-SNAPSHOT:test:%s
+            """, artifactFile.getAbsolutePath())
+        );
+        final var result = DependencyUtil.testFeatures(listFile);
+        assertNotNull(result);
+        final var composite = assertInstanceOf(OptionalCompositeOption.class, result);
+        final var options = composite.getOptions();
+        assertNotNull(options);
+        assertEquals(1, options.length);
+        final var featureOpt = assertInstanceOf(KarafFeaturesOption.class, options[0]);
+        assertEquals("mvn:org.opendaylight.odlparent/odl-test/1.2.3-SNAPSHOT/xml/features", featureOpt.getURL());
+        assertArrayEquals(new String[]{"test-feature-name"}, featureOpt.getFeatures());
+    }
+
+    @Test
+    void testDependenciesNoFeatures() throws IOException {
+        final var listFile = new File(dir, "test-dependencies");
+        writeFile(listFile, """
+            The following files have been resolved:
+               none
+            """);
+        assertEmpty(DependencyUtil.testFeatures(listFile));
+    }
+
+    @Test
+    void testDependenciesNoListFile() {
+        assertEmpty(DependencyUtil.testFeatures(new File("")));
+    }
+
+    private static void writeFile(final File targetFile, final String content) throws IOException {
+        Files.write(targetFile.toPath(), content.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private static void assertEmpty(final Option result) {
+        assertNotNull(result);
+        final var composite = assertInstanceOf(OptionalCompositeOption.class, result);
+        final var options = composite.getOptions();
+        assertNotNull(options);
+        assertEquals(0, options.length);
+    }
+}