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.Difference; import org.revapi.Report; import org.revapi.Reporter; import org.revapi.Revapi; import org.revapi.java.spi.Code; /** * @author Lukas Krejci * @since 0.1 */ public class MissingClassReportingTest extends AbstractJavaElementAnalyzerTest { private JavaArchive apiV1; private JavaArchive apiV2; private List<Report> allReports = new ArrayList<>(); private Revapi revapi; private void compileJars() throws Exception { //compile all the classes we need in 1 go AbstractJavaElementAnalyzerTest.ArchiveAndCompilationPath 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"); // 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("C.class").toFile(), "C.class"); //now do the same for v2 AbstractJavaElementAnalyzerTest.ArchiveAndCompilationPath 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"); // 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("C.class").toFile(), "C.class"); } @Before public void setup() throws Exception { compileJars(); allReports.clear(); Reporter reporter = new CollectingReporter(allReports); revapi = createRevapi(reporter); } @After public void teardown() throws Exception { revapi.close(); } @Test public void testErrorsOutOnMissingClasses() throws Exception { try { revapi.analyze( AnalysisContext.builder() .withOldAPI(API.of(new ShrinkwrapArchive(apiV1)).build()) .withNewAPI(API.of(new ShrinkwrapArchive(apiV2)).build()) .withConfigurationFromJSON("{\"revapi\" : { \"java\" : { \"missing-classes\" : \"error\" }}}") .build() ); Assert.fail(); } catch (RuntimeException e) { //expected } } @Test public void testReportsMissingClasses() throws Exception { AnalysisContext ctx = AnalysisContext.builder() .withOldAPI(API.of(new ShrinkwrapArchive(apiV1)).build()) .withNewAPI(API.of(new ShrinkwrapArchive(apiV2)).build()) .withConfigurationFromJSON( "{\"revapi\" : { \"java\" : { \"missing-classes\" : {\"behavior\" : \"report\" }}}}").build(); revapi.validateConfiguration(ctx); revapi.analyze(ctx); Assert.assertEquals(3, allReports.size()); Assert.assertTrue(containsDifference(allReports, "missing-class B.T$2", "missing-class B.T$2", Code.MISSING_IN_NEW_API.code())); Assert.assertTrue(containsDifference(allReports, "missing-class B.T$2", "missing-class B.T$2", Code.MISSING_IN_OLD_API.code())); Assert.assertTrue(containsDifference(allReports, null, "missing-class B.T$3", Code.MISSING_IN_NEW_API.code())); Assert.assertTrue(containsDifference(allReports, null, "field A.f3", Code.FIELD_ADDED.code())); boolean containsMissingOld = false; boolean containsMissingNew = false; for (Difference d : allReports.get(0).getDifferences()) { if (d.code.equals(Code.MISSING_IN_NEW_API.code())) { containsMissingNew = true; } if (d.code.equals(Code.MISSING_IN_OLD_API.code())) { containsMissingOld = true; } } Assert.assertTrue(containsMissingOld); Assert.assertTrue(containsMissingNew); } @Test public void testIgnoresMissingClasses() throws Exception { revapi.analyze( AnalysisContext.builder() .withOldAPI(API.of(new ShrinkwrapArchive(apiV1)).build()) .withNewAPI(API.of(new ShrinkwrapArchive(apiV2)).build()) .withConfigurationFromJSON( "{\"revapi\" : { \"java\" : { \"missing-classes\" : {\"behavior\" : \"ignore\" }}}}").build() ); Assert.assertEquals(1, allReports.size()); Assert.assertTrue(containsDifference(allReports, null, "field A.f3", Code.FIELD_ADDED.code())); } }