You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by vo...@apache.org on 2020/05/03 17:52:23 UTC

[fineract] 01/03: add classpath duplicates detection test (FINERACT-919)

This is an automated email from the ASF dual-hosted git repository.

vorburger pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit b0367099bc73f5637d43ffdd79a3891db6d32eef
Author: Michael Vorburger.ch <mi...@vorburger.ch>
AuthorDate: Wed Jul 3 22:49:43 2019 +0200

    add classpath duplicates detection test (FINERACT-919)
---
 fineract-provider/dependencies.gradle              |   3 +-
 .../ClasspathHellDuplicatesCheckRule.java          |  67 +++++++++
 .../ClasspathHellDuplicatesCheckRuleTest.java      |  35 +++++
 .../classdupes/ClasspathHellDuplicatesChecker.java | 157 +++++++++++++++++++++
 4 files changed, 261 insertions(+), 1 deletion(-)

diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle
index ac99b54..e701658 100644
--- a/fineract-provider/dependencies.gradle
+++ b/fineract-provider/dependencies.gradle
@@ -24,7 +24,7 @@ dependencies {
         }
 
     providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
-    
+
     spotbugsPlugins "jp.skypencil.findbugs.slf4j:bug-pattern:1.4.2@jar"
 
     compile ("org.springframework.boot:spring-boot-starter-data-jpa")
@@ -107,6 +107,7 @@ dependencies {
             'junit:junit',
             //'junit:junit-dep',
             'org.mockito:mockito-core',
+            'io.github.classgraph:classgraph:4.8.43',
             'org.slf4j:slf4j-simple',
             'com.mockrunner:mockrunner-jms',
             'com.google.code.gson:gson',
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRule.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRule.java
new file mode 100644
index 0000000..e15ae79
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRule.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.classdupes;
+
+import java.util.List;
+import java.util.Map;
+import junit.framework.AssertionFailedError;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * JUnit Rule to run detect duplicate entries on the classpath. Usage:
+ *
+ * <pre>public static {@literal @}ClassRule ClasspathHellDuplicatesCheckRule
+ *     dupes = new ClasspathHellDuplicatesCheckRule();</pre>
+ *
+ * <p>NB that the basepom/duplicate-finder-maven-plugin already runs as part of odlparent.
+ * It has a similar purpose, but covers build time instead of runtime testing.  This JUnit Rule class is
+ * thus recommended to be used in particular in tests which previously ran into JAR Hell issues, and for
+ * which non-regression with a clear failure message in case of future similar problems is important.
+ * (This provides more details at runtime than duplicate-finder-maven-plugin does at build time.)
+ *
+ * @author Michael Vorburger.ch
+ */
+public class ClasspathHellDuplicatesCheckRule implements TestRule {
+
+    private final ClasspathHellDuplicatesChecker checker;
+
+    public ClasspathHellDuplicatesCheckRule(ClasspathHellDuplicatesChecker checker) {
+        this.checker = checker;
+    }
+
+    public ClasspathHellDuplicatesCheckRule() {
+        this(ClasspathHellDuplicatesChecker.INSTANCE);
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        checkClasspath();
+        return base;
+    }
+
+    protected void checkClasspath() {
+        Map<String, List<String>> dupes = checker.getDuplicates();
+        if (!dupes.isEmpty()) {
+            throw new AssertionFailedError(dupes.size() + " Classpath duplicates detected:\n" + checker.toString(dupes));
+        }
+    }
+}
+
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRuleTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRuleTest.java
new file mode 100644
index 0000000..a81797f
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRuleTest.java
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.classdupes;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * {@link ClasspathHellDuplicatesCheckRule} test.
+ *
+ * @author Michael Vorburger
+ */
+public class ClasspathHellDuplicatesCheckRuleTest {
+
+    @ClassRule public static ClasspathHellDuplicatesCheckRule jHades = new ClasspathHellDuplicatesCheckRule();
+
+    @Test // we just need this because JUnit doesn't like a *Test class with only a Rule
+    public void testIfThereAreAnyDuplicatesOnTheClasspath() throws Exception { }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesChecker.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesChecker.java
new file mode 100644
index 0000000..d25ed42
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesChecker.java
@@ -0,0 +1,157 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.classdupes;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ResourceList;
+import io.github.classgraph.ScanResult;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+/**
+ * Check classpath for duplicates.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class ClasspathHellDuplicatesChecker {
+
+    public static final ClasspathHellDuplicatesChecker INSTANCE = new ClasspathHellDuplicatesChecker();
+
+    private final Map<String, List<String>> duplicates;
+
+    public ClasspathHellDuplicatesChecker() {
+        duplicates = recheck();
+    }
+
+    public Map<String, List<String>> getDuplicates() {
+        return duplicates;
+    }
+
+    public String toString(Map<String, List<String>> map) {
+        StringBuilder sb = new StringBuilder();
+        for (Entry<String, List<String>> entry : map.entrySet()) {
+            sb.append(entry.getKey());
+            sb.append('\n');
+            for (String location : entry.getValue()) {
+                sb.append("    ");
+                sb.append(location);
+                sb.append('\n');
+            }
+        }
+        return sb.toString();
+    }
+
+    private Map<String, List<String>> recheck() {
+        Map<String, List<String>> dupes = new HashMap<>();
+        // To debug this scanner, use ClassGraph().verbose()
+        // We intentionally do not use .classFilesOnly(), or .nonClassFilesOnly(), to check both
+        try (ScanResult scanResult = new ClassGraph().scan()) {
+            for (Entry<String, ResourceList> dupe : scanResult.getAllResources().findDuplicatePaths()) {
+                String resourceName = dupe.getKey();
+                if (!isHarmlessDuplicate(resourceName)) {
+                    boolean addIt = true;
+                    List<String> jars = dupe.getValue().stream().map(resource -> resource.getURL().toExternalForm()).collect(Collectors.toList());
+                    for (String jar : jars) {
+                        if (skipJAR(jar)) {
+                            addIt = false;
+                            break;
+                        }
+                    }
+                    if (addIt) {
+                        dupes.put(resourceName, jars);
+                    }
+                }
+            }
+            return dupes;
+        }
+    }
+
+    private boolean skipJAR(String jarPath) {
+        // ./gradlew test finds classes from the Gradle Wrapper (which don't show up in-IDE), exclude those
+        return jarPath.contains("/.gradle/wrapper/dists/");
+    }
+
+    protected boolean isHarmlessDuplicate(String resourcePath) {
+        // list from org.jhades.reports.DuplicatesReport
+        return resourcePath.equals("META-INF/MANIFEST.MF")
+                || resourcePath.equals("META-INF/INDEX.LIST")
+                || resourcePath.equals("META-INF/ORACLE_J.SF")
+                || resourcePath.toUpperCase().startsWith("META-INF/NOTICE")
+                || resourcePath.toUpperCase().startsWith("META-INF/LICENSE")
+                || resourcePath.toUpperCase().startsWith("LICENSE")
+                || resourcePath.toUpperCase().startsWith("LICENSE/NOTICE")
+                // list formerly in ClasspathHellDuplicatesCheckRule (moved here in INFRAUTILS-52)
+                || resourcePath.endsWith(".txt")
+                || resourcePath.endsWith("LICENSE")
+                || resourcePath.endsWith("license.html")
+                || resourcePath.endsWith("about.html")
+                || resourcePath.endsWith("readme.html")
+                || resourcePath.startsWith("META-INF/services")
+                || resourcePath.equals("META-INF/DEPENDENCIES")
+                || resourcePath.equals("META-INF/git.properties")
+                || resourcePath.equals("META-INF/io.netty.versions.properties")
+                || resourcePath.equals("META-INF/jersey-module-version")
+                || resourcePath.startsWith("OSGI-INF/blueprint/")
+                || resourcePath.startsWith("org/opendaylight/blueprint/")
+                || resourcePath.equals("WEB-INF/web.xml")
+                || resourcePath.endsWith("reference.conf") // in Akka's JARs
+                || resourcePath.equals("META-INF/eclipse.inf")
+                || resourcePath.equals("META-INF/ECLIPSE_.SF")
+                || resourcePath.equals("META-INF/ECLIPSE_.RSA")
+                || resourcePath.equals("META-INF/BC2048KE.DSA")
+                || resourcePath.equals("META-INF/BC2048KE.SF")
+                || resourcePath.equals("META-INF/BC1024KE.SF")
+                || resourcePath.equals("OSGI-INF/bundle.info")
+                // Something doesn't to be a perfectly clean in Maven Surefire:
+                || resourcePath.startsWith("META-INF/maven/")
+                || resourcePath.contains("surefire")
+                // org.slf4j.impl.StaticLoggerBinder.class in testutils for the LogCaptureRule
+                || resourcePath.equals("org/slf4j/impl/StaticLoggerBinder.class")
+                // INFRAUTILS-35: JavaLaunchHelper is both in java and libinstrument.dylib (?) on Mac OS X
+                || resourcePath.contains("JavaLaunchHelper")
+                // javax.annotation is a big mess... :( E.g. javax.annotation.Resource (and some others)
+                // are present both in rt.jar AND javax.annotation-api-1.3.2.jar and similar - BUT those
+                // JARs cannot just be excluded, because they contain some additional annotations, in the
+                // (reserved!) package javax.annotation, such as javax.annotation.Priority et al.  The
+                // super proper way to address this cleanly would be to make our own JAR for javax.annotation
+                // and have it contain ONLY what is not already in package javax.annotation in rt.jar.. but for now:
+                || resourcePath.equals("javax/annotation/Resource$AuthenticationType.class")
+                // NEUTRON-205: javax.inject is a mess :( because of javax.inject:javax.inject (which we widely use in ODL)
+                // VS. org.glassfish.hk2.external:javax.inject (which Glassfish Jersey has dependencies on).  Attempts to
+                // cleanly exclude glassfish.hk2's javax.inject and align everything on only depending on
+                // javax.inject:javax.inject have failed, because the OSGi bundle
+                // org.glassfish.jersey.containers.jersey-container-servlet-core (2.25.1) has a non-optional Package-Import
+                // for javax.inject, but we made javax.inject:javax.inject <optional>true in odlparent, and don't bundle it.
+                || resourcePath.startsWith("javax/inject/")
+                // Java 9 modules
+                || resourcePath.endsWith("module-info.class")
+                || resourcePath.contains("findbugs")
+                // list newly introduced in INFRAUTILS-52, because classgraph scans more than JHades did
+                || resourcePath.equals("plugin.properties")
+                || resourcePath.equals(".api_description")
+                // errorprone with Java 11 integration leaks to classpath, which causes a conflict between
+                // checkerframework/checker-qual and checkerframework/dataflow
+                || resourcePath.startsWith("org/checkerframework/dataflow/qual/")
+                ;
+    }
+}
+