Merge "Reenable running metalava integration builds"
diff --git a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
index f9be836..18efeae 100644
--- a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
+++ b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
@@ -402,14 +402,20 @@
item1.qualifiedName().compareTo((item2 as ClassItem).qualifiedName())
}
is MethodItem -> {
+ // Try to incrementally match aspects of the method until you can conclude
+ // whether they are the same or different.
+ // delta is 0 when the methods are the same, else not 0
+ // Start by comparing the names
var delta = item1.name().compareTo((item2 as MethodItem).name())
if (delta == 0) {
+ // If the names are the same then compare the number of parameters
val parameters1 = item1.parameters()
val parameters2 = item2.parameters()
val parameterCount1 = parameters1.size
val parameterCount2 = parameters2.size
delta = parameterCount1 - parameterCount2
if (delta == 0) {
+ // If the parameter count is the same, compare the parameter types
for (i in 0 until parameterCount1) {
val parameter1 = parameters1[i]
val parameter2 = parameters2[i]
@@ -417,7 +423,7 @@
val type2 = parameter2.type().toTypeString(context = parameter2)
delta = type1.compareTo(type2)
if (delta != 0) {
- // Try a little harder:
+ // If the parameter types aren't the same, try a little harder:
// (1) treat varargs and arrays the same, and
// (2) drop java.lang. prefixes from comparisons in wildcard
// signatures since older signature files may have removed
@@ -426,15 +432,21 @@
val simpleType2 = parameter2.type().toCanonicalType(parameter2)
delta = simpleType1.compareTo(simpleType2)
if (delta != 0) {
- // Special case: Kotlin coroutines
+ // If still not the same, check the special case for
+ // Kotlin coroutines: It's possible one has "experimental"
+ // when fully qualified while the other does not.
+ // We treat these the same, so strip the prefix and strip
+ // "experimental", then compare.
if (simpleType1.startsWith("kotlin.coroutines.") && simpleType2.startsWith("kotlin.coroutines.")) {
val t1 = simpleType1.removePrefix("kotlin.coroutines.").removePrefix("experimental.")
val t2 = simpleType2.removePrefix("kotlin.coroutines.").removePrefix("experimental.")
delta = t1.compareTo(t2)
if (delta != 0) {
+ // They're not the same
break
}
} else {
+ // They're not the same
break
}
}
@@ -442,6 +454,7 @@
}
}
}
+ // The method names are different, return the result of the compareTo
delta
}
is FieldItem -> {
diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
index 326a777..a6bdeda 100644
--- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
+++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
@@ -503,7 +503,9 @@
// and (b) the method is not already inferred to be 'final' by virtue of its class.
if (!old.isEffectivelyFinal() && new.isEffectivelyFinal()) {
report(
- Issues.ADDED_FINAL, new, "${describe(new, capitalize = true)} has added 'final' qualifier"
+ Issues.ADDED_FINAL,
+ new,
+ "${describe(new, capitalize = true)} has added 'final' qualifier"
)
}
}
diff --git a/src/main/java/com/android/tools/metalava/model/MemberItem.kt b/src/main/java/com/android/tools/metalava/model/MemberItem.kt
index f96d49f..95985f8 100644
--- a/src/main/java/com/android/tools/metalava/model/MemberItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/MemberItem.kt
@@ -35,6 +35,8 @@
* be final because its containing class is final
*/
fun isEffectivelyFinal(): Boolean {
- return modifiers.isFinal() || containingClass().modifiers.isFinal()
+ return modifiers.isFinal() ||
+ containingClass().modifiers.isFinal() ||
+ containingClass().modifiers.isSealed()
}
}
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
index 0302221..9829871 100644
--- a/src/main/resources/version.properties
+++ b/src/main/resources/version.properties
@@ -2,4 +2,4 @@
# Version definition
# This file is read by gradle build scripts, but also packaged with metalava
# as a resource for the Version classes to read.
-metalavaVersion=1.0.0-alpha05
+metalavaVersion=1.0.0-alpha06
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
index 499ca7f..678dea6 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -3826,6 +3826,26 @@
method default public void bar(Int);
}
}
+ """
+ )
+ }
+
+ fun `Allow change from non-final to final in sealed class`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ sealed class Foo {
+ method final public void bar(Int);
+ }
+ }
+ """,
+ format = FileFormat.V4,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ sealed class Foo {
+ method public void bar(Int);
+ }
+ }
"""
)
}
diff --git a/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassMethodsAndConstructors.kt b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassMethodsAndConstructors.kt
new file mode 100644
index 0000000..b7abbf3
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassMethodsAndConstructors.kt
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.android.tools.metalava.binarycompatibility
+
+import com.android.tools.metalava.DriverTest
+import org.junit.Test
+
+class BinaryCompatibilityClassMethodsAndConstructors : DriverTest() {
+ @Test
+ fun `Change method name`() {
+ check(
+ expectedIssues = """
+ TESTROOT/released-api.txt:3: error: Removed method test.pkg.Foo.bar(Int) [RemovedMethod]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void baz(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ // Note: This is reversed from the eclipse wiki because of kotlin named parameters
+ fun `Change formal parameter name (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/load-api.txt:3: error: Attempted to change parameter name from bread to toast in method test.pkg.Foo.bar [ParameterNameChange]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int toast);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int bread);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Add or delete formal parameter (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/released-api.txt:3: error: Removed method test.pkg.Foo.bar(Int) [RemovedMethod]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar();
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change type of a formal parameter (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/released-api.txt:3: error: Removed method test.pkg.Foo.bar(Int) [RemovedMethod]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Float);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change result type (including void) (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed return type from void to Int [ChangedType]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public Int bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Add checked exceptions thrown (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar added thrown exception java.lang.Throwable [ChangedThrows]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int) throws java.lang.Throwable;
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Delete checked exceptions thrown (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar no longer throws exception java.lang.Throwable [ChangedThrows]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int) throws java.lang.Throwable;
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Re-order list of exceptions thrown (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int) throws java.lang.Exception, java.lang.Throwable;
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int) throws java.lang.Throwable, java.lang.Exception;
+ }
+ }
+ """
+ )
+ }
+ @Test
+ /*
+ Decrease access; that is, from protected access to default or private access,
+ or from public access to protected, default, or private access
+ */
+ fun `Decrease access(Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar changed visibility from public to protected [ChangedScope]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method protected void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Increase access, that is, from protected access to public access (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method protected void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change abstract to non-abstract (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ abstract class Foo {
+ method public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ abstract class Foo {
+ method abstract public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change non-abstract to abstract (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'abstract' qualifier [ChangedAbstract]
+ """,
+ signatureSource = """
+ package test.pkg {
+ abstract class Foo {
+ method abstract public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ abstract class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change final to non-final (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method final public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Change non-final to final (method not re-implementable) (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ sealed class Foo {
+ method final public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ sealed class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change non-final to final (method re-implementable) (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has added 'final' qualifier [AddedFinal]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method final public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change static to non-static (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'static' qualifier [ChangedStatic]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method static public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change non-static to static (Incompatible)`() {
+ check(
+ expectedIssues = """
+ TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'static' qualifier [ChangedStatic]
+ """,
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method static public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change native to non-native (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method native public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change non-native to native (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method native public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change synchronized to non-synchronized (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method synchronized public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change non-synchronized to synchronized (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method synchronized public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+
+ /*
+ TODO: Fix b/217229076 and uncomment this block of tests
+
+ @Test
+ fun `Add type parameter (existing type parameters) (Incompatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public <T, K> void bar();
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public <T> void bar();
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Add type parameter (no existing type parameters) (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public <T> void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Delete type parameter (Incompatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public <T> void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Re-order type parameters (Incompatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public <T, K> void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public <K, T> void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Rename type parameter (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public <T> void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public <K> void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Add, delete, or change type bounds of parameter (Incompatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public <T extends Foo> void bar(Int);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public <T> void bar(Int);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change last parameter from array type T(array) to variable arity T(elipse) (Compatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public <T> void bar(T...);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public <T> void bar(T[]);
+ }
+ }
+ """
+ )
+ }
+ @Test
+ fun `Change last parameter from variable arity T(elipse) to array type T(array) (Incompatible)`() {
+ check(
+ signatureSource = """
+ package test.pkg {
+ class Foo {
+ method public <T> void bar(T[]);
+ }
+ }
+ """,
+ checkCompatibilityApiReleased = """
+ package test.pkg {
+ class Foo {
+ method public <T> void bar(T...);
+ }
+ }
+ """
+ )
+ }
+
+ */
+}