/* * Copyright 2008 Google Inc. * * 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 com.google.gwt.tools.apichecker; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.NotFoundException; import com.google.gwt.dev.javac.testing.impl.StaticJavaResource; import com.google.gwt.dev.resource.Resource; import com.google.gwt.dev.util.log.AbstractTreeLogger; import com.google.gwt.dev.util.log.PrintWriterTreeLogger; import junit.framework.TestCase; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; /** * * Test 2 ApiContainers for compatibility * * test compatibility output if returnType changes, parameters change, method * overload compatibility, exception compatibility; abstract added, final added * to both ApiClass, apiMethods. * * test white-list support. * * TODO(amitmanjhi): (1) Re-factor this code as much as possible into smaller * separate components similar to the ApiCompatibilityUnit class. (2) Use * MockApiElement instead of String comparisons. */ public class ApiCompatibilityTest extends TestCase { // These cups are slightly different from the cups in ApiContainerTest private static final boolean DEBUG = false; private static StaticJavaResource[] getScuArray() { return new StaticJavaResource[] { new StaticJavaResource("test.apicontainer.ApiClass", getSourceForApiClass()), new StaticJavaResource("test.apicontainer.NonApiClass", getSourceForNonApiClass()), new StaticJavaResource("test.nonapipackage.TestClass", getSourceForTestClass()), new StaticJavaResource("java.lang.Object", getSourceForObject()), new StaticJavaResource("java.lang.Throwable", getSourceForThrowable()), new StaticJavaResource("test.apicontainer.OneMoreApiClass", getSourceForOneMoreApiClass()), new StaticJavaResource("java.lang.RuntimeException", getSourceForRuntimeException()),}; } private static String getSourceForApiClass() { StringBuffer sb = new StringBuffer(); sb.append("package test.apicontainer;\n"); sb.append("public class ApiClass extends NonApiClass {\n"); sb.append("\tpublic void apiMethod() { };\n"); sb.append("\tpublic void checkParametersAndReturnTypes(java.lang.Object x) throws java.lang.Throwable { };\n"); sb.append("\tpublic final void checkParametersAndReturnTypesFinalVersion(java.lang.Object x) throws java.lang.Throwable { };\n"); sb.append("};\n"); return sb.toString(); } private static String getSourceForNonApiClass() { StringBuffer sb = new StringBuffer(); sb.append("package test.apicontainer;\n"); sb.append("class NonApiClass extends java.lang.Object {\n"); sb.append("\tpublic void methodInNonApiClass(ApiClassInNonApiClass o) { };\n"); sb.append("\tpublic void methodInNonApiClass(NonApiClass t) { };\n"); sb.append("\tpublic int fieldInNonApiClass = 3;\n"); sb.append("\tprotected abstract class ApiClassInNonApiClass {\n"); sb.append("\tprotected ApiClassInNonApiClass() { }\n"); sb.append("\t}\n"); sb.append("}\n"); return sb.toString(); } private static String getSourceForObject() { StringBuffer sb = new StringBuffer(); sb.append("package java.lang;\n"); sb.append("public class Object {\n"); sb.append("\tpublic void apiMethod() { }\n"); sb.append("\tprotected void checkOverloadedAndOverridableDetection(java.lang.Object b) { }\n"); sb.append("\tprotected final void checkOverloadedMethodAccounted(java.lang.Object b) { }\n"); sb.append("\tprivate void internalMethod() { }\n"); sb.append("\tprotected final void protectedMethod() { }\n"); sb.append("\tpublic final int apiField = 0;\n"); sb.append("\tprivate int internalField = 0;\n"); sb.append("\tprotected static int protectedField=2;\n"); sb.append("}\n"); return sb.toString(); } private static String getSourceForOneMoreApiClass() { StringBuffer sb = new StringBuffer(); sb.append("package test.apicontainer;\n"); sb.append("public class OneMoreApiClass extends java.lang.Object {\n"); sb.append("\tprotected void checkOverloadedAndOverridableDetection(test.apicontainer.OneMoreApiClass b) { }\n"); sb.append("\tprotected final void checkOverloadedMethodAccounted(test.apicontainer.ApiClass b) throws java.lang.Throwable { }\n"); sb.append("\tpublic void testUncheckedExceptions() throws RuntimeException { }\n"); sb.append("};\n"); return sb.toString(); } private static String getSourceForRuntimeException() { StringBuffer sb = new StringBuffer(); sb.append("package java.lang;\n"); sb.append("public class RuntimeException extends Throwable {\n"); sb.append("}\n"); return sb.toString(); } private static String getSourceForTestClass() { StringBuffer sb = new StringBuffer(); sb.append("package test.nonapipackage;\n"); sb.append("class TestClass extends java.lang.Object {\n"); sb.append("\tpublic void method() { }\n"); sb.append("}\n"); return sb.toString(); } private static String getSourceForThrowable() { StringBuffer sb = new StringBuffer(); sb.append("package java.lang;\n"); sb.append("public class Throwable extends Object {\n"); sb.append("}\n"); return sb.toString(); } ApiContainer api1 = null; ApiContainer api2 = null; ApiContainer apiSameAs1 = null; @Override public void setUp() throws UnableToCompleteException { AbstractTreeLogger logger = new PrintWriterTreeLogger(); logger.setMaxDetail(TreeLogger.ERROR); api1 = new ApiContainer("Api1", new HashSet<Resource>(Arrays .asList(ApiContainerTest.getScuArray())), new HashSet<String>(), logger); apiSameAs1 = new ApiContainer("ApiSameAs1", new HashSet<Resource>(Arrays.asList(ApiContainerTest .getScuArray())), new HashSet<String>(), logger); api2 = new ApiContainer("Api2", new HashSet<Resource>(Arrays.asList(getScuArray())), new HashSet<String>(), logger); } // setup is called before every test*. To avoid the overhead of setUp() each // time, test everything together. public void testEverything() throws NotFoundException { checkBasicStuff(); checkWhiteList(); } private void checkBasicStuff() throws NotFoundException { HashSet<String> hashSet = new HashSet<String>(); assertEquals(0, ApiCompatibilityChecker.getApiDiff(api1, apiSameAs1, hashSet).size()); ApiDiffGenerator apiDiff = new ApiDiffGenerator(api2, api1); String strWithDuplicates = getStringRepresentation(ApiCompatibilityChecker.getApiDiff(apiDiff, hashSet, !ApiCompatibilityChecker.FILTER_DUPLICATES)); if (DEBUG) { System.out.println("computing apiDiff, now with duplicates"); System.out.println(strWithDuplicates); } String strWithoutDuplicates = getStringRepresentation(ApiCompatibilityChecker.getApiDiff(apiDiff, hashSet, ApiCompatibilityChecker.FILTER_DUPLICATES)); if (DEBUG) { System.out.println("computing apiDiff, now without duplicates"); System.out.println(strWithoutDuplicates); } String delimiter = ApiDiffGenerator.DELIMITER; // test if missing packages are reported correctly String statusString = "java.newpackage" + delimiter + ApiChange.Status.MISSING; assertEquals(1, countPresence(statusString, strWithDuplicates)); assertEquals(1, countPresence(statusString, strWithoutDuplicates)); // test if missing classes are reported correctly assertEquals(1, countPresence("test.apicontainer.NonApiClass.AnotherApiClassInNonApiClass" + delimiter + ApiChange.Status.MISSING, strWithoutDuplicates)); // test if modifier changes of a class are reported assertEquals(1, countPresence("test.apicontainer.NonApiClass.ApiClassInNonApiClass" + delimiter + ApiChange.Status.ABSTRACT_ADDED, strWithoutDuplicates)); // test if methods are still reported even if class becomes abstract (as // long as it is sub-classable) assertEquals(0, countPresence( "test.apicontainer.NonApiClass.ApiClassInNonApiClass::ApiClassInNonApiClass()" + delimiter + ApiChange.Status.MISSING, strWithoutDuplicates)); assertEquals(0, countPresence( "test.apicontainer.NonApiClass.ApiClassInNonApiClass::protectedMethod()" + delimiter + ApiChange.Status.MISSING, strWithoutDuplicates)); // test if modifier changes of fields and methods are reported assertEquals(1, countPresence("java.lang.Object::apiField" + delimiter + ApiChange.Status.FINAL_ADDED, strWithoutDuplicates)); assertEquals(1, countPresence("java.lang.Object::protectedMethod()" + delimiter + ApiChange.Status.FINAL_ADDED, strWithoutDuplicates)); // test if duplicates are weeded out from intersecting methods assertEquals(4, countPresence("protectedMethod()" + delimiter + ApiChange.Status.FINAL_ADDED, strWithDuplicates)); assertEquals(1, countPresence("protectedMethod()" + delimiter + ApiChange.Status.FINAL_ADDED, strWithoutDuplicates)); // test if duplicates are weeded out from missing fields assertEquals(4, countPresence("apiFieldWillBeMissing" + delimiter + ApiChange.Status.MISSING, strWithDuplicates)); assertEquals(1, countPresence("apiFieldWillBeMissing" + delimiter + ApiChange.Status.MISSING, strWithoutDuplicates)); // test error in non-final version String nonFinalMethodSignature = "checkParametersAndReturnTypes(Ltest/apicontainer/ApiClass;)"; for (ApiChange.Status status : new ApiChange.Status[] { ApiChange.Status.OVERRIDABLE_METHOD_ARGUMENT_TYPE_CHANGE, ApiChange.Status.OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE, ApiChange.Status.OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE}) { assertEquals(1, countPresence(nonFinalMethodSignature + delimiter + status, strWithoutDuplicates)); } // test return type and exception type error in final version String finalMethodSignature = "checkParametersAndReturnTypesFinalVersion(Ltest/apicontainer/ApiClass;)"; assertEquals(1, countPresence(finalMethodSignature + delimiter + ApiChange.Status.RETURN_TYPE_ERROR, strWithoutDuplicates)); assertEquals(1, countPresence(finalMethodSignature + delimiter + ApiChange.Status.EXCEPTION_TYPE_ERROR, strWithoutDuplicates)); // checking if changes in parameter types were okay assertEquals(2, countPresence(finalMethodSignature, strWithoutDuplicates)); // test method_overloading finalMethodSignature = "methodInNonApiClass(Ltest/apicontainer/NonApiClass;)"; assertEquals(1, countPresence(finalMethodSignature + delimiter + ApiChange.Status.OVERLOADED_METHOD_CALL, strWithoutDuplicates)); // test unchecked exceptions assertEquals(0, countPresence("testUncheckedExceptions", strWithoutDuplicates)); // test overloaded and overridable detection String methodSignature = "test.apicontainer.OneMoreApiClass::checkOverloadedAndOverridableDetection(Ljava/lang/Object;)"; for (ApiChange.Status status : new ApiChange.Status[] { ApiChange.Status.OVERRIDABLE_METHOD_ARGUMENT_TYPE_CHANGE, ApiChange.Status.OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE, ApiChange.Status.OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE}) { assertEquals(0, countPresence(methodSignature + delimiter + status, strWithoutDuplicates)); } // the method should be satisfied by the method in the super-class methodSignature = "test.apicontainer.OneMoreApiClass::checkOverloadedMethodAccounted(Ltest/apicontainer/OneMoreApiClass;)"; assertEquals(0, countPresence(methodSignature + delimiter + ApiChange.Status.MISSING, strWithoutDuplicates)); // the method should throw unchecked exceptions error methodSignature = "test.apicontainer.OneMoreApiClass::checkOverloadedMethodAccounted(Ljava/lang/Object;)"; assertEquals(1, countPresence(methodSignature + delimiter + ApiChange.Status.EXCEPTION_TYPE_ERROR, strWithoutDuplicates)); } private void checkWhiteList() throws NotFoundException { ApiDiffGenerator apiDiff = new ApiDiffGenerator(api2, api1); boolean removeDuplicates = false; String whiteList = "java.newpackage" + ApiDiffGenerator.DELIMITER + ApiChange.Status.MISSING; HashSet<String> hashSet = new HashSet<String>(); hashSet.add(whiteList); String strWithoutDuplicates = getStringRepresentation(ApiCompatibilityChecker.getApiDiff(apiDiff, hashSet, !removeDuplicates)); // test if missing packages are reported correctly assertEquals(0, countPresence(whiteList, strWithoutDuplicates)); } private int countPresence(String needle, String hay) { int count = 0; int needleLength = needle.length(); int index = -needleLength; while ((index = hay.indexOf(needle, index + needleLength)) != -1) { count++; } return count; } private String getStringRepresentation(Collection<ApiChange> collection) { StringBuffer sb = new StringBuffer(); for (ApiChange apiChange : collection) { sb.append(apiChange); sb.append("\n"); } return sb.toString(); } } // abstract methods can't be static class TestAA { static int j = 10; static void printX() { System.err.println("2"); } int i = 5; private TestAA() { } } class TestAB { static void test() { TestAA.printX(); } }