blob: ce90559a91c513f951893b67dbc7b5ffe3197c94 [file] [log] [blame]
/*
* 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();
}
}