/* * Copyright 2014 Lukas Krejci * * Licensed 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.revapi.java; import java.util.ArrayList; import java.util.List; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.revapi.API; import org.revapi.AnalysisContext; import org.revapi.Report; import org.revapi.Reporter; import org.revapi.Revapi; import org.revapi.java.spi.Code; import org.revapi.java.spi.JavaModelElement; /** * @author Lukas Krejci * @since 0.1 */ public class SupplementaryJarsTest extends AbstractJavaElementAnalyzerTest { private ArchiveAndCompilationPath compRes1; private ArchiveAndCompilationPath compRes2; private JavaArchive apiV1; private JavaArchive apiV2; private JavaArchive supV1; private JavaArchive supV2; @Before public void compile() throws Exception { //compile all the classes we need in 1 go compRes1 = createCompiledJar("tmp1", "v1/supplementary/a/A.java", "v1/supplementary/b/B.java", "v1/supplementary/a/C.java"); //now, create 2 jars out of them. Class A will be our "api" jar and the rest of the classes will form the //supplementary jar that needs to be present as a runtime dep of the API but isn't itself considered an API of //of its own. //We then check that types from such supplementary jar that the API jar "leaks" by exposing them as types //in public/protected fields/methods/method params are then considered the part of the API during api checks. apiV1 = ShrinkWrap.create(JavaArchive.class, "apiV1.jar") .addAsResource(compRes1.compilationPath.resolve("A.class").toFile(), "A.class") .addAsResource(compRes1.compilationPath.resolve("C.class").toFile(), "C.class"); supV1 = ShrinkWrap.create(JavaArchive.class, "supV1.jar") .addAsResource(compRes1.compilationPath.resolve("B.class").toFile(), "B.class") .addAsResource(compRes1.compilationPath.resolve("B$T$1.class").toFile(), "B$T$1.class") .addAsResource(compRes1.compilationPath.resolve("B$T$1$TT$1.class").toFile(), "B$T$1$TT$1.class") .addAsResource(compRes1.compilationPath.resolve("B$T$2.class").toFile(), "B$T$2.class") .addAsResource(compRes1.compilationPath.resolve("B$UsedByIgnoredClass.class").toFile(), "B$UsedByIgnoredClass.class") .addAsResource(compRes1.compilationPath.resolve("A$PrivateEnum.class").toFile(), "A$PrivateEnum.class"); //now do the same for v2 compRes2 = createCompiledJar("tmp2", "v2/supplementary/a/A.java", "v2/supplementary/b/B.java", "v2/supplementary/a/C.java"); apiV2 = ShrinkWrap.create(JavaArchive.class, "apiV2.jar") .addAsResource(compRes2.compilationPath.resolve("A.class").toFile(), "A.class") .addAsResource(compRes2.compilationPath.resolve("C.class").toFile(), "C.class"); supV2 = ShrinkWrap.create(JavaArchive.class, "supV2.jar") .addAsResource(compRes2.compilationPath.resolve("B.class").toFile(), "B.class") .addAsResource(compRes2.compilationPath.resolve("B$T$1.class").toFile(), "B$T$1.class") .addAsResource(compRes2.compilationPath.resolve("B$T$1$TT$1.class").toFile(), "B$T$1$TT$1.class") .addAsResource(compRes2.compilationPath.resolve("B$T$2.class").toFile(), "B$T$2.class") .addAsResource(compRes2.compilationPath.resolve("B$T$1$Private.class").toFile(), "B$T$1$Private.class") .addAsResource(compRes2.compilationPath.resolve("B$T$3.class").toFile(), "B$T$3.class") .addAsResource(compRes2.compilationPath.resolve("B$PrivateSuperClass.class").toFile(), "B$PrivateSuperClass.class") .addAsResource(compRes2.compilationPath.resolve("B$PrivateUsedClass.class").toFile(), "B$PrivateUsedClass.class") .addAsResource(compRes2.compilationPath.resolve("B$UsedByIgnoredClass.class").toFile(), "B$UsedByIgnoredClass.class") .addAsResource(compRes1.compilationPath.resolve("A$PrivateEnum.class").toFile(), "A$PrivateEnum.class"); } @After public void delete() throws Exception { deleteDir(compRes1.compilationPath); deleteDir(compRes2.compilationPath); } @Test public void testSupplementaryJarsAreTakenIntoAccountWhenComputingAPI() throws Exception { List<Report> allReports = new ArrayList<>(); Reporter reporter = new CollectingReporter(allReports); try (Revapi revapi = createRevapi(reporter)) { revapi.analyze(AnalysisContext.builder() .withOldAPI(API.of(new ShrinkwrapArchive(apiV1)).supportedBy(new ShrinkwrapArchive(supV1)).build()) .withNewAPI(API.of(new ShrinkwrapArchive(apiV2)).supportedBy(new ShrinkwrapArchive(supV2)).build()) .build()); } Assert.assertEquals(8 + 11, allReports.size()); //11 removed methods when kind of class changes to interface Assert.assertTrue( containsDifference(allReports, null, "class B.T$1.Private", Code.CLASS_NON_PUBLIC_PART_OF_API.code())); Assert.assertTrue(containsDifference(allReports, null, "field B.T$2.f2", Code.FIELD_ADDED.code())); Assert.assertTrue(containsDifference(allReports, null, "field A.f3", Code.FIELD_ADDED.code())); Assert.assertTrue(containsDifference(allReports, "class B.T$2", "class B.T$2", Code.CLASS_NOW_FINAL.code())); Assert.assertTrue(containsDifference(allReports, null, "class B.T$3", Code.CLASS_ADDED.code())); Assert.assertTrue(containsDifference(allReports, null, "class B.PrivateUsedClass", Code.CLASS_NON_PUBLIC_PART_OF_API.code())); Assert.assertTrue(containsDifference(allReports, "class B.UsedByIgnoredClass", "class B.UsedByIgnoredClass", Code.CLASS_KIND_CHANGED.code())); Assert.assertTrue(containsDifference(allReports, "method void B.UsedByIgnoredClass::<init>()", null, Code.METHOD_REMOVED.code())); //eleven methods removed when kind changed, because interface doesn't have the methods of Object Assert.assertEquals(11, allReports.stream() .filter(r -> { javax.lang.model.element.TypeElement oldType = null; if (r.getOldElement() == null || !(r.getOldElement() instanceof JavaModelElement)) { return false; } javax.lang.model.element.Element old = ((JavaModelElement) r.getOldElement()).getDeclaringElement(); do { if (old instanceof javax.lang.model.element.TypeElement) { oldType = (javax.lang.model.element.TypeElement) old; break; } old = old.getEnclosingElement(); } while (old != null); if (oldType == null) { return false; } return oldType.getQualifiedName().contentEquals("java.lang.Object"); }) .flatMap(r -> r.getDifferences().stream()) .count()); } @Test public void testExcludedClassesDontDragUsedTypesIntoAPI() throws Exception { List<Report> allReports = new ArrayList<>(); Reporter reporter = new CollectingReporter(allReports); try (Revapi revapi = createRevapi(reporter)) { revapi.analyze(AnalysisContext.builder() .withOldAPI(API.of(new ShrinkwrapArchive(apiV1)).supportedBy(new ShrinkwrapArchive(supV1)).build()) .withNewAPI(API.of(new ShrinkwrapArchive(apiV2)).supportedBy(new ShrinkwrapArchive(supV2)).build()) .withConfigurationFromJSON("{\"revapi\": {\"java\": {" + "\"filter\": {\"classes\": {\"exclude\": [\"C\"]}}}}}").build()); } Assert.assertEquals(6, allReports.size()); Assert.assertTrue( containsDifference(allReports, null, "class B.T$1.Private", Code.CLASS_NON_PUBLIC_PART_OF_API.code())); Assert.assertTrue(containsDifference(allReports, null, "field B.T$2.f2", Code.FIELD_ADDED.code())); Assert.assertTrue(containsDifference(allReports, null, "field A.f3", Code.FIELD_ADDED.code())); Assert.assertTrue(containsDifference(allReports, "class B.T$2", "class B.T$2", Code.CLASS_NOW_FINAL.code())); Assert.assertTrue(containsDifference(allReports, null, "class B.T$3", Code.CLASS_ADDED.code())); Assert.assertTrue(containsDifference(allReports, null, "class B.PrivateUsedClass", Code.CLASS_NON_PUBLIC_PART_OF_API.code())); Assert.assertFalse(containsDifference(allReports, "class B.UsedByIgnoredClass", "class B.UsedByIgnoredClass", Code.CLASS_KIND_CHANGED.code())); Assert.assertFalse(containsDifference(allReports, "method void B.UsedByIgnoredClass::<init>()", null, Code.METHOD_REMOVED.code())); } }