Bug 8175: Improve SFT TestBundleDiag under Karaf 4 09/53109/17
authorMichael Vorburger <vorburger@redhat.com>
Fri, 10 Mar 2017 00:46:39 +0000 (01:46 +0100)
committerMichael Vorburger <vorburger@redhat.com>
Thu, 6 Apr 2017 23:34:42 +0000 (01:34 +0200)
This does NOT yet completely fix all problems of the SingleFeatureTest
TestBundleDiag under Karaf 4 (which was in really bad shape!), most
importantly there is still something somewhere in Pax Exam that I
haven't understood yet which seems to just "swallow" all exceptions
thrown by SingleFeatureTest's @Test installFeature() but at least now:

1. there is something in the log now for failures at least
   thanks to a (try/catch/log) WORKAROUND (!)

2. LinkageError fixed (see details below)

3. IllegalStateException: BundleContext is no longer valid fixed (?)

This changes the setup by just embedding bundles4-test and it's
dependencies into features4-test, instead of programmatically installing
the odl-bundles-test feature (which is removed in this change) or the
features4-test bundle.

This simplifies installation, avoid problems with the wrap: protocol,
and should resolve weird problems we've seen (only!) in the distribution
jobs, which seem to have had something to do with a custom local Maven
repo.

IMHO it would be *REALLY* good to practice proper TDD for SFT (with
TestBundleDiag) now, and have some automated negative testing of broken
features and bundles (incl. BP) in odlparent...

Bug: 7981 related later proper solution
Change-Id: I516c0f6c4e9ee85f0c753cb4b58a0b53fc22c7ea
Signed-off-by: Michael Vorburger <vorburger@redhat.com>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
bundles4-test/pom.xml
bundles4-test/src/main/java/org/opendaylight/odlparent/bundles4test/BundleDiagInfos.java
bundles4-test/src/main/java/org/opendaylight/odlparent/bundles4test/TestBundleDiag.java
features4-test/pom.xml
features4-test/src/main/java/org/opendaylight/odlparent/featuretest/PerFeatureRunNotifier.java
features4-test/src/main/java/org/opendaylight/odlparent/featuretest/PerFeatureRunner.java
features4-test/src/main/java/org/opendaylight/odlparent/featuretest/ReflectionUtil.java [new file with mode: 0644]
features4-test/src/main/java/org/opendaylight/odlparent/featuretest/SingleFeatureTest.java
features4-test/src/test/java/org/opendaylight/odlparent/featuretest/ReflectionUtilTest.java [new file with mode: 0644]
features4/odl-bundles-test/pom.xml [deleted file]
features4/pom.xml

index a245fe990c4d506a2ebd5bedff190f964744a986..6d673aa432fc0c0f779aa40fc4db216db0e6a4a0 100644 (file)
     <dependency>
       <groupId>org.osgi</groupId>
       <artifactId>org.osgi.core</artifactId>
-      <scope>compile</scope>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.karaf.bundle</groupId>
       <artifactId>org.apache.karaf.bundle.core</artifactId>
       <version>${karaf4.version}</version>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.hamcrest</groupId>
-      <artifactId>hamcrest-library</artifactId>
-      <scope>compile</scope>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.hamcrest</groupId>
       <groupId>org.awaitility</groupId>
       <artifactId>awaitility</artifactId>
       <scope>compile</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>cglib</groupId>
+          <artifactId>cglib-nodep</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.objenesis</groupId>
+          <artifactId>objenesis</artifactId>
+        </exclusion>
+      </exclusions>
     </dependency>
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-    </dependency>
+    <!-- BEWARE of adding additional dependencies here...
+         It will cause weird issues e.g. in integration/distribution/features/repos/distribution -->
     <dependency>
       <groupId>com.google.truth</groupId>
       <artifactId>truth</artifactId>
 
   <build>
     <plugins>
+      <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <configuration>
+            <instructions>
+              <Embed-Dependency>*;scope=compile</Embed-Dependency>
+              <Embed-Transitive>true</Embed-Transitive>
+              <Import-Package>!net.sf.cglib.proxy,!org.objenesis,*</Import-Package>
+            </instructions>
+          </configuration>
+      </plugin>
       <plugin>
         <artifactId>maven-checkstyle-plugin</artifactId>
         <configuration>
index 28e73819cfae2be2ebde31b4b162381e66fa9587..be25ba6ab270ffa9bfca021c749f33a40ff9ef1a 100644 (file)
@@ -10,11 +10,10 @@ package org.opendaylight.odlparent.bundles4test;
 import static org.apache.karaf.bundle.core.BundleState.Active;
 import static org.apache.karaf.bundle.core.BundleState.Installed;
 
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.EnumMap;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.apache.karaf.bundle.core.BundleInfo;
@@ -30,20 +29,24 @@ import org.osgi.framework.BundleContext;
  */
 public final class BundleDiagInfos {
 
+    private static final Map<String, BundleState> WHITELISTED_BUNDLES;
+
+    static {
+        WHITELISTED_BUNDLES = new HashMap<>();
+        WHITELISTED_BUNDLES.put("slf4j.log4j12", Installed);
+    }
+
     private final List<String> okBundleStateInfoTexts;
     private final List<String> nokBundleStateInfoTexts;
     private final List<String> whitelistedBundleStateInfoTexts;
     private final Map<BundleState, Integer> bundleStatesCounters;
 
-    private static final Map<String, BundleState> WHITELISTED_BUNDLES = ImmutableMap.of(
-            "slf4j.log4j12", Installed);
-
     private BundleDiagInfos(List<String> okBundleStateInfoTexts, List<String> nokBundleStateInfoTexts,
             List<String> whitelistedBundleStateInfoTexts, Map<BundleState, Integer> bundleStatesCounters) {
-        this.okBundleStateInfoTexts = ImmutableList.copyOf(okBundleStateInfoTexts);
-        this.nokBundleStateInfoTexts = ImmutableList.copyOf(nokBundleStateInfoTexts);
-        this.whitelistedBundleStateInfoTexts = ImmutableList.copyOf(whitelistedBundleStateInfoTexts);
-        this.bundleStatesCounters = ImmutableMap.copyOf(bundleStatesCounters);
+        this.okBundleStateInfoTexts = immutableCopyOf(okBundleStateInfoTexts);
+        this.nokBundleStateInfoTexts = immutableCopyOf(nokBundleStateInfoTexts);
+        this.whitelistedBundleStateInfoTexts = immutableCopyOf(whitelistedBundleStateInfoTexts);
+        this.bundleStatesCounters = immutableCopyOf(bundleStatesCounters);
     }
 
     public static BundleDiagInfos forContext(BundleContext bundleContext, BundleService bundleService) {
@@ -63,7 +66,7 @@ public final class BundleDiagInfos {
             String bundleStateDiagText = "OSGi state = " + bundleStatetoText(bundle.getState())
                 + ", Karaf bundleState = " + karafBundleState;
             String diagText = bundleService.getDiag(bundle);
-            if (!Strings.isNullOrEmpty(diagText)) {
+            if (!diagText.isEmpty()) {
                 bundleStateDiagText += ", due to: " + diagText;
             }
 
@@ -151,19 +154,28 @@ public final class BundleDiagInfos {
     }
 
     public List<String> getNokBundleStateInfoTexts() {
-        return ImmutableList.copyOf(nokBundleStateInfoTexts);
+        return immutableCopyOf(nokBundleStateInfoTexts);
     }
 
     public List<String> getOkBundleStateInfoTexts() {
-        return ImmutableList.copyOf(okBundleStateInfoTexts);
+        return immutableCopyOf(okBundleStateInfoTexts);
     }
 
     public List<String> getWhitelistedBundleStateInfoTexts() {
-        return ImmutableList.copyOf(whitelistedBundleStateInfoTexts);
+        return immutableCopyOf(whitelistedBundleStateInfoTexts);
     }
 
     @Override
     public String toString() {
         return getFullDiagnosticText();
     }
+
+    private List<String> immutableCopyOf(List<String> stringList) {
+        return Collections.unmodifiableList(new ArrayList<>(stringList));
+    }
+
+    private Map<BundleState, Integer> immutableCopyOf(Map<BundleState, Integer> map) {
+        return Collections.unmodifiableMap(new HashMap<>(map));
+    }
+
 }
index 44a04ff7d85224f01cbba9ab26a002e637ae5b60..a6ec619e23daed571335c83267745b6d32b2285a 100644 (file)
@@ -9,7 +9,6 @@ package org.opendaylight.odlparent.bundles4test;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.junit.Assert.fail;
 
 import java.util.concurrent.TimeUnit;
 import org.apache.karaf.bundle.core.BundleService;
@@ -31,11 +30,26 @@ public class TestBundleDiag {
     private static final Logger LOG = LoggerFactory.getLogger(TestBundleDiag.class);
 
     private final BundleContext bundleContext;
+    private final ServiceReference<BundleService> bundleServiceReference;
     private final BundleService bundleService;
 
-    public TestBundleDiag(BundleContext bundleContext, BundleService bundleService) {
+    private TestBundleDiag(BundleContext bundleContext) {
         this.bundleContext = bundleContext;
-        this.bundleService = bundleService;
+        this.bundleServiceReference = bundleContext.getServiceReference(BundleService.class);
+        this.bundleService = bundleContext.getService(bundleServiceReference);
+    }
+
+    private void close() {
+        bundleContext.ungetService(bundleServiceReference);
+    }
+
+    public static void checkBundleDiagInfos(BundleContext bundleContext, long timeout, TimeUnit timeoutUnit) {
+        TestBundleDiag diag = new TestBundleDiag(bundleContext);
+        try {
+            diag.checkBundleDiagInfos(timeout, timeoutUnit);
+        } finally {
+            diag.close();
+        }
     }
 
     /**
@@ -48,7 +62,8 @@ public class TestBundleDiag {
      *
      * @author Michael Vorburger, based on guidance from Christian Schneider
      */
-    public void checkBundleDiagInfos(long timeout, TimeUnit timeoutUnit) {
+    private void checkBundleDiagInfos(long timeout, TimeUnit timeoutUnit) {
+        LOG.info("checkBundleDiagInfos() started...");
         try {
             Awaitility.await("checkBundleDiagInfos")
                 .pollDelay(0, MILLISECONDS)
@@ -67,7 +82,7 @@ public class TestBundleDiag {
                 LOG.error("diag failure; BundleService reports bundle(s) which failed or are already stopping"
                         + " (details in following INFO and ERROR log messages...)");
                 logBundleDiagInfos(bundleInfos);
-                fail(bundleInfos.getFullDiagnosticText());
+                throw new AssertionError(bundleInfos.getFullDiagnosticText());
 
             } else {
                 // Inform the developer of the green SystemState.Active
index 44d3908d1705c1036ece2af613ab0236cbba79fe..52a044d78ad6f5c2f39b1934d8aac0929081520a 100644 (file)
             <artifactId>slf4j-api</artifactId>
             <scope>compile</scope>
         </dependency>
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
-            <scope>compile</scope>
-        </dependency>
         <dependency>
             <groupId>javax.inject</groupId>
             <artifactId>javax.inject</artifactId>
-            <version>1</version>
             <scope>compile</scope>
         </dependency>
         <dependency>
             <artifactId>org.osgi.core</artifactId>
             <scope>compile</scope>
         </dependency>
-        <dependency>
-            <groupId>${project.groupId}</groupId>
-            <artifactId>odl-bundles-test</artifactId>
-            <version>${project.version}</version>
-            <type>xml</type>
-            <classifier>features</classifier>
-        </dependency>
         <dependency>
             <groupId>org.apache.karaf</groupId>
             <artifactId>apache-karaf</artifactId>
             <version>${karaf4.version}</version>
             <type>zip</type>
         </dependency>
+        <!-- BEWARE of adding additional dependencies here...
+             It will cause weird issues e.g. in integration/distribution/features/repos/distribution -->
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>bundles4-test</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+          <groupId>com.google.guava</groupId>
+          <artifactId>guava</artifactId>
+          <scope>compile</scope>
+        </dependency>
+        <!-- The following, which are just transitive dependencies of bundles4-test,
+             have to be repeated here so that the Embed-Dependency below for maven-bundle-plugin works: -->
+        <dependency>
+          <groupId>org.hamcrest</groupId>
+          <artifactId>hamcrest-core</artifactId>
+          <scope>provided</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.awaitility</groupId>
+          <artifactId>awaitility</artifactId>
+          <scope>provided</scope>
+          <exclusions>
+            <exclusion>
+              <groupId>cglib</groupId>
+              <artifactId>cglib-nodep</artifactId>
+            </exclusion>
+            <exclusion>
+              <groupId>org.objenesis</groupId>
+              <artifactId>objenesis</artifactId>
+            </exclusion>
+          </exclusions>
+        </dependency>
+        <!-- Real <scope>test dependencies, just for SingleFeatureTestUnitTest -->
+        <dependency>
+          <groupId>com.google.truth</groupId>
+          <artifactId>truth</artifactId>
+          <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                  <instructions>
+                    <Embed-Dependency>bundles4-test;inline=true,awaitility;inline=true,hamcrest-core;inline=true</Embed-Dependency>
+                    <!-- same as in bundles4-test/pom.xml: -->
+                    <Import-Package>!net.sf.cglib.proxy,!org.objenesis,*</Import-Package>
+                  </instructions>
+                </configuration>
             </plugin>
             <plugin>
                 <artifactId>maven-checkstyle-plugin</artifactId>
index 590d63d8bded5c29958651d3dd43dda684ccc1a2..4000882f6b27fbff38387dc8e11713f1982fcb3b 100644 (file)
@@ -7,9 +7,9 @@
  */
 package org.opendaylight.odlparent.featuretest;
 
-import com.google.common.base.Preconditions;
 import java.net.URL;
 import java.rmi.NoSuchObjectException;
+import java.util.Objects;
 import org.junit.runner.Description;
 import org.junit.runner.Result;
 import org.junit.runner.notification.Failure;
@@ -33,10 +33,10 @@ public class PerFeatureRunNotifier extends RunNotifier {
      */
     public PerFeatureRunNotifier(
             final URL repoUrl, final String featureName, final String featureVersion, final RunNotifier delegate) {
-        this.repoUrl = Preconditions.checkNotNull(repoUrl);
-        this.featureName = Preconditions.checkNotNull(featureName);
-        this.featureVersion = Preconditions.checkNotNull(featureVersion);
-        this.delegate = Preconditions.checkNotNull(delegate);
+        this.repoUrl = Objects.requireNonNull(repoUrl);
+        this.featureName = Objects.requireNonNull(featureName);
+        this.featureVersion = Objects.requireNonNull(featureVersion);
+        this.delegate = Objects.requireNonNull(delegate);
     }
 
     private Failure convertFailure(final Failure failure) {
index f14a401bb17c3d18245f25a991a8e982e4bdbce2..ef5157e9463f9c80785d27182c0eb7955c13b2b9 100644 (file)
@@ -12,8 +12,8 @@ import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_
 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 com.google.common.base.Preconditions;
 import java.net.URL;
+import java.util.Objects;
 import org.junit.runner.Description;
 import org.junit.runner.Runner;
 import org.junit.runner.manipulation.Filter;
@@ -48,14 +48,14 @@ public class PerFeatureRunner extends Runner implements Filterable, Sortable {
     public PerFeatureRunner(
             final URL repoUrl, final String featureName, final String featureVersion, final Class<?> testClass)
             throws InitializationError {
-        this.repoUrl = Preconditions.checkNotNull(repoUrl);
-        this.featureName = Preconditions.checkNotNull(featureName);
-        this.featureVersion = Preconditions.checkNotNull(featureVersion);
+        this.repoUrl = Objects.requireNonNull(repoUrl);
+        this.featureName = Objects.requireNonNull(featureName);
+        this.featureVersion = Objects.requireNonNull(featureVersion);
 
         System.setProperty(ORG_OPENDAYLIGHT_FEATURETEST_URI_PROP, repoUrl.toString());
         System.setProperty(ORG_OPENDAYLIGHT_FEATURETEST_FEATURENAME_PROP, featureName);
         System.setProperty(ORG_OPENDAYLIGHT_FEATURETEST_FEATUREVERSION_PROP, featureVersion);
-        this.delegate = new PaxExam(Preconditions.checkNotNull(testClass));
+        this.delegate = new PaxExam(Objects.requireNonNull(testClass));
     }
 
     @Override
diff --git a/features4-test/src/main/java/org/opendaylight/odlparent/featuretest/ReflectionUtil.java b/features4-test/src/main/java/org/opendaylight/odlparent/featuretest/ReflectionUtil.java
new file mode 100644 (file)
index 0000000..4476ec2
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc. 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.reflect.ClassPath;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+import org.ops4j.pax.exam.TestProbeBuilder;
+
+/**
+ * Utility for class path reflection.
+ *
+ * @author Michael Vorburger.ch
+ */
+public final class ReflectionUtil {
+
+    private ReflectionUtil() {}
+
+    public static void addAllClassesInSameAndSubPackageOfClass(TestProbeBuilder probe, Class<?> clazz) {
+        addAllClassesInSameAndSubPackageOfPackage(probe, clazz.getPackage().getName());
+    }
+
+    public static void addAllClassesInSameAndSubPackageOfPackage(TestProbeBuilder probe, String packageName) {
+        getClasses(ReflectionUtil.class.getClassLoader(), packageName).forEach(eachClass -> probe.addTest(eachClass));
+    }
+
+    /**
+     * Returns all classes in the named package, and its sub-packages.
+     */
+    public static Stream<Class<?>> getClasses(ClassLoader classLoader, String packageName) {
+        try {
+            ClassPath classPath = ClassPath.from(classLoader);
+            // inspired by https://github.com/vorburger/ch.vorburger.minecraft.osgi/blob/master/ch.vorburger.minecraft.osgi/src/main/java/ch/vorburger/osgi/embedded/PackagesBuilder.java
+            return classPath.getTopLevelClassesRecursive(packageName)
+                    .stream().map(classInfo -> classInfo.load())
+                     // to include all inner classes, including anonymous inner classes:
+                    .flatMap(clazz -> getDeclaredAndAnonymousInnerClass(clazz));
+        } catch (IOException e) {
+            throw new IllegalStateException("ClassPath.from(classLoader) failed", e);
+        }
+    }
+
+    private static Stream<Class<?>> getDeclaredAndAnonymousInnerClass(Class<?> clazz) {
+        List<Class<?>> anonymousInnerClasses = new ArrayList<>();
+        anonymousInnerClasses.add(clazz); // add self; will get skipped if empty() below!
+        anonymousInnerClasses.addAll(Arrays.asList(clazz.getDeclaredClasses()));
+        ClassLoader classLoader = clazz.getClassLoader();
+        String className = clazz.getCanonicalName();
+        for (int i = 1; ; i++) {
+            try {
+                anonymousInnerClasses.add(classLoader.loadClass(className + "$" + i));
+            } catch (ClassNotFoundException e) {
+                // Last anonymous inner class found (even none), so we're done, return:
+                return anonymousInnerClasses.stream();
+            } catch (NoClassDefFoundError e) {
+                // Oups, this class cannot be loaded, so return empty stream so that flatMap() removes it!
+                return Stream.empty();
+            }
+        }
+    }
+
+
+}
index 33d273ead1e70faf8096dd05c525ba61886a0e82..50567939eec9b6a1b61544f47f2eafb13015cf17 100644 (file)
@@ -19,24 +19,25 @@ import static org.ops4j.pax.exam.CoreOptions.systemPackages;
 import static org.ops4j.pax.exam.CoreOptions.when;
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;
-import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.features;
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
 
-import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Properties;
 import javax.inject.Inject;
-import org.apache.karaf.bundle.core.BundleService;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
+import org.awaitility.Awaitility;
 import org.eclipse.jdt.annotation.NonNull;
 import org.junit.Assert;
 import org.junit.Before;
@@ -45,8 +46,9 @@ import org.junit.runner.RunWith;
 import org.opendaylight.odlparent.bundles4test.TestBundleDiag;
 import org.ops4j.pax.exam.Configuration;
 import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.ProbeBuilder;
+import org.ops4j.pax.exam.TestProbeBuilder;
 import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;
-import org.ops4j.pax.exam.options.MavenUrlReference;
 import org.ops4j.pax.exam.options.extra.VMOption;
 import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
@@ -115,12 +117,29 @@ public class SingleFeatureTest {
     @Inject @NonNull
     private FeaturesService featuresService;
 
-    @Inject @NonNull
-    private BundleService bundleService; // NOT BundleStateService, see checkBundleStatesDiag()
-
     private String karafVersion;
     private String karafDistroVersion;
 
+    @ProbeBuilder
+    public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) {
+        // add this to test Karaf Commands, according to green Karaf book
+        // also see http://iocanel.blogspot.ch/2012/01/advanced-integration-testing-with-pax.html
+        // probe.setHeader(org.osgi.framework.Constants.DYNAMICIMPORT_PACKAGE, "*;status=provisional");
+
+        // adding these so that loading of TestBundleDiag and its dependencies works
+        // NB that here in the features4-test this works completely differently than
+        // in the original features-test for Karaf 3; there we installed the bundle in
+        // config() whereas here we embed dependencies into a single JAR to simplify
+        // problems we've had in distribution jobs with custom local Maven repos;
+        // but because of this we have to "help" Pax Exam with what classes need
+        // to be bundled with its probe:
+        ReflectionUtil.addAllClassesInSameAndSubPackageOfClass(probe, TestBundleDiag.class);
+        ReflectionUtil.addAllClassesInSameAndSubPackageOfClass(probe, Awaitility.class);
+        ReflectionUtil.addAllClassesInSameAndSubPackageOfPackage(probe, "com.google.common");
+
+        return probe;
+    }
+
     /**
      * Returns the required configuration.
      *
@@ -129,13 +148,6 @@ public class SingleFeatureTest {
      */
     @Configuration
     public Option[] config() throws IOException {
-        MavenUrlReference bundleTestRepo = maven()
-                .groupId("org.opendaylight.odlparent")
-                .artifactId("odl-bundles-test")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-
         return new Option[] {
             // TODO: Find a way to inherit memory limits from Maven options.
             new VMOption("-Xmx2g"),
@@ -157,16 +169,15 @@ public class SingleFeatureTest {
             getKarafDistroOption(),
             when(Boolean.getBoolean(KEEP_UNPACK_DIRECTORY_PROP)).useOptions(keepRuntimeFolder()),
             configureConsole().ignoreLocalConsole(),
-            logLevel(LogLevel.WARN),
+            logLevel(LogLevel.INFO),
             mvnLocalRepoOption(),
-            features(bundleTestRepo, "odl-bundles-test"),
             mavenBundle("org.apache.aries.quiesce", "org.apache.aries.quiesce.api", "1.0.0"),
             editConfigurationFilePut(ORG_OPS4J_PAX_LOGGING_CFG, LOG4J_LOGGER_ORG_OPENDAYLIGHT_YANGTOOLS_FEATURETEST,
                     LogLevel.INFO.name()),
             editConfigurationFilePut("etc/config.properties", "karaf.framework", "equinox"),
             editConfigurationFilePut(ETC_ORG_OPS4J_PAX_LOGGING_CFG, "log4j.rootLogger", "INFO, stdout, osgi:*"),
+
              /*
-              *
               * Disables external snapshot repositories.
               *
               * Pax URL and Karaf by default always search for new version of snapshots
@@ -185,8 +196,6 @@ public class SingleFeatureTest {
               * In order to speed-up installation and remove unnecessary network traffic,
               * which fails for obvious reasons, external snapshot repositories are
               * removed.
-              *
-              *
               */
             disableExternalSnapshotRepositories(),
             propagateSystemProperty(ORG_OPENDAYLIGHT_FEATURETEST_URI_PROP),
@@ -320,21 +329,63 @@ public class SingleFeatureTest {
 
     // Give it 10 minutes max as we've seen feature install hang on jenkins.
     @Test(timeout = 600000)
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    public void installFeatureCatchAndLog() throws Exception {
+        // TODO remove this when the underlying problem is solved
+        // https://bugs.opendaylight.org/show_bug.cgi?id=7981:
+        // "SFT never fails, Pax Exam (or our wrappers) swallow all exceptions"
+        try {
+            installFeature();
+        } catch (Throwable t) {
+            LOG.error("installFeature() failed", t);
+            // as of 2017.03.20, this re-throw seems to have no effect,
+            // the exception gets lost in space, swallowed somewhere! :(
+            throw t;
+        }
+    }
+
     public void installFeature() throws Exception {
+        // The BundleContext originally @Inject'd into the field
+        // is, as expected, the PAXEXAM-PROBE.  For some strange reason,
+        // under Karaf 4 (this works under Karaf 3 without this trick),
+        // after the installFeature() & getFeature() & isInstalled()
+        // below are through, that BundleContext has become invalid
+        // already (too soon!), and using it leads to "IllegalStateException:
+        // BundleContext is no longer valid". -- Because we don't actually
+        // need the PAXEXAM-PROBE, just ANY BundleContext, we employ a
+        // little trick, and obtain the OSGi Framework's (Felix or Equinox's)
+        // own BundleContext, which will never become invalid, and use that instead.
+        // This works, but is a work around, and the fact that we have to do this
+        // may be an indication of a larger problem... see also related strange open bugs
+        // which make it seem like at least some other bundles also get uninstalled
+        // way too soon, for some reason:
+        //  * https://bugs.opendaylight.org/show_bug.cgi?id=7924
+        //  * https://bugs.opendaylight.org/show_bug.cgi?id=7923 (?)
+        //  * https://bugs.opendaylight.org/show_bug.cgi?id=7926
+        bundleContext = bundleContext.getBundle(0).getBundleContext();
+
         LOG.info("Attempting to install feature {} {}", getFeatureName(), getFeatureVersion());
-        featuresService.installFeature(getFeatureName(), getFeatureVersion());
+        featuresService.installFeature(getFeatureName(), getFeatureVersion(),
+                EnumSet.of(FeaturesService.Option.Verbose));
+        LOG.info("installFeature() completed");
         Feature feature = featuresService.getFeature(getFeatureName(), getFeatureVersion());
+        LOG.info("getFeature() completed");
         Assert.assertNotNull(
-                "Attempt to get feature " + getFeatureName() + " " + getFeatureVersion() + "resulted in null", feature);
-        Assert.assertTrue("Failed to install Feature: " + getFeatureName() + " " + getFeatureVersion(),
-                featuresService.isInstalled(feature));
-        LOG.info("Successfull installed feature {} {}", getFeatureName(), getFeatureVersion());
+                "Attempt to get feature " + getFeatureName() + " " + getFeatureVersion() + "resulted in null",
+                feature);
+        boolean isInstalled = featuresService.isInstalled(feature);
+        LOG.info("isInstalled() completed");
+        Assert.assertTrue(
+                "Failed to install Feature: " + getFeatureName() + " " + getFeatureVersion(), isInstalled);
+        LOG.info("Successfully installed feature {} {}", getFeatureName(), getFeatureVersion());
 
         if (!Boolean.getBoolean(BUNDLES_DIAG_SKIP_PROP)
                 && (Boolean.getBoolean(BUNDLES_DIAG_FORCE_PROP)
                     || !BLACKLISTED_BROKEN_FEATURES.contains(getFeatureName()))) {
+            LOG.info("new TestBundleDiag().checkBundleDiagInfos() STARTING");
             Integer timeOutInSeconds = Integer.getInteger(BUNDLES_DIAG_TIMEOUT_PROP, 5 * 60);
-            new TestBundleDiag(bundleContext, bundleService).checkBundleDiagInfos(timeOutInSeconds, SECONDS);
+            TestBundleDiag.checkBundleDiagInfos(bundleContext, timeOutInSeconds, SECONDS);
+            LOG.info("new TestBundleDiag().checkBundleDiagInfos() ENDED");
         } else {
             LOG.warn("SKIPPING TestBundleDiag because system property {} is true or feature is blacklisted: {}",
                     BUNDLES_DIAG_SKIP_PROP, getFeatureName());
@@ -342,7 +393,7 @@ public class SingleFeatureTest {
     }
 
     // TODO remove this when all issues linked to parent https://bugs.opendaylight.org/show_bug.cgi?id=7582 are resolved
-    private static final List<String> BLACKLISTED_BROKEN_FEATURES = ImmutableList.of(
+    private static final List<String> BLACKLISTED_BROKEN_FEATURES = new ArrayList<>(Arrays.asList(
             // integration/distribution/features-test due to DOMRpcService
             // see https://bugs.opendaylight.org/show_bug.cgi?id=7595
             "odl-integration-all",
@@ -352,14 +403,6 @@ public class SingleFeatureTest {
             "odl-mdsal-clustering-commons",
             "odl-mdsal-distributed-datastore",
             "odl-mdsal-remoterpc-connector",
-            // Karaf 4 specific problems with genius features
-            // see https://bugs.opendaylight.org/show_bug.cgi?id=8100
-            "odl-genius",
-            "odl-genius-api",
-            "odl-genius-rest",
-            "odl-genius-ui",
-            "odl-genius-fcaps-framework",
-            "odl-genius-fcaps-application",
             // 1/17 in groupbasedpolicy/features due to NOK org.opendaylight.groupbasedpolicy
             // Caused by: org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException
             // see https://bugs.opendaylight.org/show_bug.cgi?id=7587
@@ -371,5 +414,5 @@ public class SingleFeatureTest {
             // 1/9 in unimgr/features due missing mdsal, similar to issue to odl-integration-all?
             // TODO retry after https://bugs.opendaylight.org/show_bug.cgi?id=7595 is fixed
             "odl-unimgr-netvirt"
-    );
+    ));
 }
diff --git a/features4-test/src/test/java/org/opendaylight/odlparent/featuretest/ReflectionUtilTest.java b/features4-test/src/test/java/org/opendaylight/odlparent/featuretest/ReflectionUtilTest.java
new file mode 100644 (file)
index 0000000..3ba035f
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc. 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 com.google.common.truth.Truth.assertThat;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+/**
+ * Unit test of {@link ReflectionUtil}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class ReflectionUtilTest {
+
+    @Test
+    public void testGetClasses() {
+        assertThat(ReflectionUtil.getClasses(getClass().getClassLoader(), "org.awaitility")
+                .collect(Collectors.toList()))
+                .containsAllOf(org.awaitility.Awaitility.class, org.awaitility.core.ConditionTimeoutException.class);
+    }
+
+    @Test
+    public void testGetInnerClasses() {
+        List<Class<?>> innerClasses = ReflectionUtil.getClasses(
+                getClass().getClassLoader(), getClass().getPackage().getName()).collect(Collectors.toList());
+        assertThat(innerClasses).containsAllOf(getClass(), InnerStaticClass.class, InnerNonStaticClass.class);
+        assertThat(innerClasses.stream().anyMatch(
+            clazz -> clazz.getName().endsWith(getClass().getSimpleName() + "$1"))).isTrue();
+        assertThat(innerClasses).containsNoDuplicates();
+    }
+
+    @SuppressWarnings("unused")
+    private final InnerNonStaticClass anoymousInnerClass = new InnerNonStaticClass() { };
+
+    private static class InnerStaticClass { }
+
+    private class InnerNonStaticClass { }
+
+}
diff --git a/features4/odl-bundles-test/pom.xml b/features4/odl-bundles-test/pom.xml
deleted file mode 100644 (file)
index 1690e84..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- Copyright © 2017 Red Hat, Inc. 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
- -->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <groupId>org.opendaylight.odlparent</groupId>
-        <artifactId>single-feature-parent</artifactId>
-        <version>1.8.0-SNAPSHOT</version>
-        <relativePath>../../single-feature-parent</relativePath>
-    </parent>
-
-    <name>ODL :: odlparent :: ${project.artifactId}</name>
-
-    <groupId>org.opendaylight.odlparent</groupId>
-    <artifactId>odl-bundles-test</artifactId>
-    <version>1.8.0-SNAPSHOT</version>
-    <packaging>feature</packaging>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.opendaylight.odlparent</groupId>
-            <artifactId>bundles4-test</artifactId>
-            <version>1.8.0-SNAPSHOT</version>
-        </dependency>
-        <!-- We need to force these to compile scope -->
-        <dependency>
-            <groupId>org.awaitility</groupId>
-            <artifactId>awaitility</artifactId>
-            <scope>compile</scope>
-        </dependency>
-    </dependencies>
-</project>
\ No newline at end of file
index c872e8f68e47f2452a617cbb9b012aa7ffeb4f77..ad50583a491ce6c4945fd8a203cd43d69e2c4eb0 100644 (file)
@@ -49,7 +49,6 @@
         <module>odl-triemap-0.2</module>
 
         <!-- Test features -->
-        <module>odl-bundles-test</module>
     </modules>
 
     <build>