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 <[email protected]> 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/") + ; + } +} +
