blob: 44f501b85a4e365d1fe6a501f78b8ed4e984d07f [file] [log] [blame]
/*
* Copyright (C) 2017 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
import org.intellij.lang.annotations.Language
import org.junit.Test
class ApiFromTextTest : DriverTest() {
@Test
fun `Loading a signature file and writing the API back out`() {
val source =
"""
package test.pkg {
public class MyTest {
ctor public MyTest();
method public int clamp(int);
method public Double convert(Float);
field public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
field public Number myNumber;
}
}
"""
check(format = FileFormat.V2, signatureSource = source, api = source)
}
@Test
fun `Handle lambdas as default values`() {
val source =
"""
// Signature format: 3.0
package androidx.collection {
public final class LruCacheKt {
ctor public LruCacheKt();
method public static <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { null as V? }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
}
}
"""
check(
format = FileFormat.V3,
signatureSource = source,
includeSignatureVersion = true,
api = source
)
}
@Test
fun `Invoking function with multiple parameters as parameter default value`() {
val source =
"""
// Signature format: 3.0
package abc {
public final class PopupKt {
method public static void DropdownPopup(Type ident = SomeFunc(SomeVal, SomeVal));
}
}
"""
check(
format = FileFormat.V3,
signatureSource = source,
includeSignatureVersion = true,
api = source
)
}
@Test
fun `Handle enum constants as default values`() {
val source =
"""
// Signature format: 3.0
package test.pkg {
public final class Foo {
ctor public Foo();
method public android.graphics.Bitmap? drawToBitmap(android.view.View, android.graphics.Bitmap.Config config = android.graphics.Bitmap.Config.ARGB_8888);
method public void emptyLambda(kotlin.jvm.functions.Function0<kotlin.Unit> sizeOf = {});
method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args);
method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);
method public void method3(String str, int p, int int2 = double(int) + str.length);
field public static final test.pkg.Foo.Companion! Companion;
}
public static final class Foo.Companion {
method public int double(int p);
method public void print(test.pkg.Foo foo = test.pkg.Foo());
}
public final class LruCacheKt {
ctor public LruCacheKt();
method public static <K, V> android.util.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (V)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
}
}
"""
check(
format = FileFormat.V3,
signatureSource = source,
includeSignatureVersion = true,
api = source
)
}
@Test
fun `Handle complex expressions as default values`() {
val source =
"""
// Signature format: 3.0
package androidx.paging {
public final class PagedListConfigKt {
ctor public PagedListConfigKt();
method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = PagedList.Config.MAX_SIZE_UNBOUNDED);
}
public final class PagedListKt {
ctor public PagedListKt();
method public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, Key? initialKey = null);
}
}
package test.pkg {
public final class Foo {
ctor public Foo();
method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args);
method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);
method public void method3(str: String = "unbalanced), string", str2: String = ",");
}
}
"""
check(
format = FileFormat.V3,
signatureSource = source,
includeSignatureVersion = true,
api = source
)
}
@Test
fun `Annotation signatures requiring more complicated token matching`() {
val source =
"""
package test {
public class MyTest {
method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public boolean addAccountExplicitly(android.accounts.Account, String, android.os.Bundle);
method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,\"foo\",String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
method @RequiresPermission(anyOf={"android.permission.MANAGE_ACCOUNTS", "android.permission.USE_CREDENTIALS"}, apis="..22") public void invalidateAuthToken(String, String);
}
}
"""
check(
format = FileFormat.V2,
outputKotlinStyleNulls = false,
signatureSource = source,
api = source
)
}
@Test
fun `Multiple extends`() {
val source =
"""
package test {
public static interface PickConstructors extends test.pkg.PickConstructors.AutoCloseable {
}
public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable {
method public void close();
}
}
"""
check(outputKotlinStyleNulls = false, signatureSource = source, api = source)
}
@Test
fun `Native and strictfp keywords`() {
check(
outputKotlinStyleNulls = false,
signatureSource =
"""
package test.pkg {
public class MyTest {
method public native float dotWithNormal(float, float, float);
method public static strictfp double toDegrees(double);
}
}
""",
api =
"""
package test.pkg {
public class MyTest {
method public float dotWithNormal(float, float, float);
method public static double toDegrees(double);
}
}
"""
)
}
@Test
fun `Type use annotations`() {
check(
format = FileFormat.V2,
outputKotlinStyleNulls = false,
signatureSource =
"""
package test.pkg {
public class MyTest {
method public static int codePointAt(char @NonNull [], int);
method @NonNull public java.util.Set<java.util.Map.@NonNull Entry<K,V>> entrySet();
method @NonNull public java.lang.annotation.@NonNull Annotation @NonNull [] getAnnotations();
method @NonNull public abstract java.lang.annotation.@NonNull Annotation @NonNull [] @NonNull [] getParameterAnnotations();
method @NonNull public @NonNull String @NonNull [] split(@NonNull String, int);
method public static char @NonNull [] toChars(int);
}
}
""",
api =
"""
package test.pkg {
public class MyTest {
method public static int codePointAt(char @NonNull [], int);
method @NonNull public java.util.Set<java.util.Map.@NonNull Entry<K,V>> entrySet();
method @NonNull public java.lang.annotation.Annotation @NonNull [] getAnnotations();
method @NonNull public abstract java.lang.annotation.Annotation @NonNull [] @NonNull [] getParameterAnnotations();
method @NonNull public String @NonNull [] split(@NonNull String, int);
method public static char @NonNull [] toChars(int);
}
}
"""
)
}
@Test
fun `Vararg modifier`() {
val source =
"""
package test.pkg {
public final class Foo {
ctor public Foo();
method public void error(int p = "42", Integer int2 = "null", int p1 = "42", vararg String args);
}
}
"""
check(outputKotlinStyleNulls = false, signatureSource = source)
}
@Test
fun `Infer fully qualified names from shorter names`() {
check(
format = FileFormat.V2,
signatureSource =
"""
package test.pkg {
public class MyTest {
ctor public MyTest();
method public int clamp(int);
method public double convert(@Nullable Float, byte[], Iterable<java.io.File>);
}
}
""",
api =
"""
package test.pkg {
public class MyTest {
ctor public MyTest();
method public int clamp(int);
method public double convert(@Nullable Float, byte[], Iterable<java.io.File>);
}
}
"""
)
}
@Test
fun `Loading a signature file with alternate modifier order`() {
// Regression test for https://github.com/android/android-ktx/issues/242
val source =
"""
package test.pkg {
deprecated public class MyTest {
ctor deprecated public Foo(int, int);
method deprecated public static final void edit(android.content.SharedPreferences, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
field deprecated public static java.util.List<java.lang.String> LIST;
}
}
"""
check(
format = FileFormat.V2,
signatureSource = source,
api =
"""
package test.pkg {
@Deprecated public class MyTest {
ctor @Deprecated public MyTest(int, int);
method @Deprecated public static final void edit(android.content.SharedPreferences, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
field @Deprecated public static java.util.List<java.lang.String> LIST;
}
}
"""
)
}
@Test
fun `Test generics, superclasses and interfaces`() {
val source =
"""
package a.b.c {
public interface MyStream<T, S extends a.b.c.MyStream<T, S>> {
}
}
package test.pkg {
public enum Foo {
ctor public Foo(int);
ctor public Foo(int, int);
method public static test.pkg.Foo valueOf(String);
method public static final test.pkg.Foo[] values();
enum_constant public static final test.pkg.Foo A;
enum_constant public static final test.pkg.Foo B;
}
public interface MyBaseInterface {
}
public interface MyInterface<T> extends test.pkg.MyBaseInterface {
}
public interface MyInterface2<T extends java.lang.Number> extends test.pkg.MyBaseInterface {
}
public abstract static class MyInterface2.Range<T extends java.lang.Comparable<? super T>> {
ctor public MyInterface2.Range();
}
public static class MyInterface2.TtsSpan<C extends test.pkg.MyInterface<?>> {
ctor public MyInterface2.TtsSpan();
}
public final class Test<T> {
ctor public Test();
method public abstract <T extends java.util.Collection<java.lang.String>> T addAllTo(T);
method public static <T & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T>);
method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws java.lang.Throwable;
field public static java.util.List<java.lang.String> LIST;
}
}
"""
check(format = FileFormat.V2, signatureSource = source, api = source)
}
@Test
fun `Test constants`() {
val source =
"""
package test.pkg {
public class Foo2 {
ctor public Foo2();
field public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
field public static final char HEX_INPUT = 61184; // 0xef00 '\uef00'
field protected int field00;
field public static final boolean field01 = true;
field public static final int field02 = 42; // 0x2a
field public static final long field03 = 42L; // 0x2aL
field public static final short field04 = 5; // 0x5
field public static final byte field05 = 5; // 0x5
field public static final char field06 = 99; // 0x0063 'c'
field public static final float field07 = 98.5f;
field public static final double field08 = 98.5;
field public static final String field09 = "String with \"escapes\" and \u00a9...";
field public static final double field10 = (0.0/0.0);
field public static final double field11 = (1.0/0.0);
}
}
"""
check(signatureSource = source, api = source)
}
@Test
fun `Test inner classes`() {
val source =
"""
package test.pkg {
public abstract class Foo {
ctor public Foo();
method @Deprecated public static final void method1();
method @Deprecated public static final void method2();
}
@Deprecated protected static final class Foo.Inner1 {
ctor protected Foo.Inner1();
}
@Deprecated protected abstract static class Foo.Inner2 {
ctor protected Foo.Inner2();
}
@Deprecated protected static interface Foo.Inner3 {
method public default void method3();
method public abstract static void method4(int);
}
}
"""
check(signatureSource = source, api = source)
}
@Test
fun `Test throws`() {
val source =
"""
package test.pkg {
public final class Test<T> {
ctor public Test();
method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws java.lang.Throwable;
}
}
"""
check(format = FileFormat.V2, signatureSource = source, api = source)
}
@Test
fun `Loading a signature file with annotations on classes, fields, methods and parameters`() {
@Language("TEXT")
val source =
"""
// Signature format: 3.0
package test.pkg {
@UiThread public class MyTest {
ctor public MyTest();
method @IntRange(from=10, to=20) public int clamp(int);
method public Double? convert(Float myPublicName);
field public Number? myNumber;
}
}
"""
check(format = FileFormat.V3, signatureSource = source, api = source)
}
@Test
fun `Enums`() {
val source =
"""
package test.pkg {
public enum Foo {
enum_constant public static final test.pkg.Foo A;
enum_constant public static final test.pkg.Foo B;
}
}
"""
check(outputKotlinStyleNulls = false, signatureSource = source, api = source)
}
@Test
fun `Annotations`() {
val source =
"""
package android.annotation {
public @interface SuppressLint {
method public abstract String[] value();
}
}
"""
check(outputKotlinStyleNulls = false, signatureSource = source, api = source)
}
@Test
fun `Annotations on packages`() {
val source =
"""
package @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) test.pkg {
public abstract class Class1 {
ctor public Class1();
}
}
"""
check(outputKotlinStyleNulls = false, signatureSource = source, api = source)
}
@Test
fun `Sort throws list by full name`() {
check(
format = FileFormat.V2,
signatureSource =
"""
package android.accounts {
public abstract interface AccountManagerFuture<V> {
method public abstract boolean cancel(boolean);
method public abstract V getResult() throws android.accounts.OperationCanceledException, java.io.IOException, android.accounts.AuthenticatorException;
method public abstract V getResult(long, java.util.concurrent.TimeUnit) throws android.accounts.OperationCanceledException, java.io.IOException, android.accounts.AuthenticatorException;
method public abstract boolean isCancelled();
method public abstract boolean isDone();
}
public class AuthenticatorException extends java.lang.Throwable {
}
public class OperationCanceledException extends java.lang.Throwable {
}
}
""",
api =
"""
package android.accounts {
public interface AccountManagerFuture<V> {
method public boolean cancel(boolean);
method public V getResult() throws android.accounts.AuthenticatorException, java.io.IOException, android.accounts.OperationCanceledException;
method public V getResult(long, java.util.concurrent.TimeUnit) throws android.accounts.AuthenticatorException, java.io.IOException, android.accounts.OperationCanceledException;
method public boolean isCancelled();
method public boolean isDone();
}
public class AuthenticatorException extends java.lang.Throwable {
}
public class OperationCanceledException extends java.lang.Throwable {
}
}
"""
)
}
@Test
fun `Loading a signature file with default values`() {
@Language("TEXT")
val source =
"""
// Signature format: 3.0
package test.pkg {
public final class Foo {
ctor public Foo();
method public final void error(int p = 42, Integer? int2 = null);
}
public class Foo2 {
ctor public Foo2();
method public void foo(String! = null, String! = "(Hello) World", int = 42);
}
}
"""
check(format = FileFormat.V3, signatureSource = source, api = source)
}
@Test
fun `Signatures with default annotation method values`() {
val source =
"""
// Signature format: 3.0
package libcore.util {
public @interface NonNull {
method public abstract int from() default java.lang.Integer.MIN_VALUE;
method public abstract double fromWithCast() default (double)java.lang.Float.NEGATIVE_INFINITY;
method public abstract String myString() default "This is a \"string\"";
method public abstract int to() default java.lang.Integer.MAX_VALUE;
}
}
"""
check(format = FileFormat.V3, signatureSource = source, api = source)
}
@Test
fun `Signatures with many annotations`() {
val source =
"""
// Signature format: 2.0
package libcore.util {
@java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface NonNull {
method public abstract int from() default java.lang.Integer.MIN_VALUE;
method public abstract int to() default java.lang.Integer.MAX_VALUE;
}
}
package test.pkg {
public class Test {
ctor public Test();
method @NonNull public Object compute();
}
}
"""
check(format = FileFormat.V2, signatureSource = source, api = source)
}
@Test
fun `Kotlin Properties`() {
val source =
"""
// Signature format: 2.0
package test.pkg {
public final class Kotlin {
ctor public Kotlin(String property1, int arg2);
method public String getProperty1();
method public String getProperty2();
method public void setProperty2(String p);
property public final String property2;
}
}
"""
check(format = FileFormat.V2, signatureSource = source, api = source)
}
@Test
fun `Deprecated enum constant`() {
val source =
"""
// Signature format: 3.0
package androidx.annotation {
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface RestrictTo {
method public abstract androidx.annotation.RestrictTo.Scope[] value();
}
public enum RestrictTo.Scope {
enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES;
enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
}
}
"""
check(
format = FileFormat.V3,
outputKotlinStyleNulls = true,
signatureSource = source,
api = source
)
}
@Test
fun `Type parameters in v3 format`() {
val source =
"""
// Signature format: 3.0
package androidx.collection {
public class Constants {
field public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
field public static final char HEX_INPUT = 61184; // 0xef00 '\uef00'
field protected int field00;
field public static final boolean field01 = true;
field public static final int field02 = 42; // 0x2a
field public static final String field09 = "String with \"escapes\" and \u00a9...";
}
public class MyMap<Key, Value> {
method public Key! getReplacement(Key!);
}
}
package androidx.paging {
public abstract class DataSource<Key, Value> {
method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
method @AnyThread public void invalidate();
method @WorkerThread public boolean isInvalid();
method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
}
public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key, Value> {
method public abstract Key getKey(Value);
method public boolean isContiguous();
method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key>, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value>);
method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
}
}
"""
check(
format = FileFormat.V3,
outputKotlinStyleNulls = true,
signatureSource = source,
api = source
)
}
@Test
fun `Signatures with reified in type parameters`() {
val source =
"""
package test.pkg {
public final class TestKt {
ctor public TestKt();
method public static inline <T> void a(T);
method public static inline <reified T> void b(T);
method public static inline <reified T> void e(T);
method public static inline <reified T> void f(T, T);
}
}
"""
check(format = FileFormat.V2, signatureSource = source, api = source)
}
@Test
fun `Suspended methods`() {
val source =
"""
package test.pkg {
public final class TestKt {
ctor public TestKt();
method public static suspend inline Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit>);
}
}
"""
check(format = FileFormat.V2, signatureSource = source, api = source)
}
@Test
fun `Complicated annotations`() {
val source =
"""
package android.app {
public static class ActionBar {
field @android.view.ViewDebug.ExportedProperty(category="layout", mapping={@android.view.ViewDebug.IntToString(from=0xffffffff, to="NONE"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.NO_GRAVITY, to="NONE"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.TOP, to="TOP"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.BOTTOM, to="BOTTOM"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.LEFT, to="LEFT"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.RIGHT, to="RIGHT"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.START, to="START"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.END, to="END"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.CENTER_VERTICAL, to="CENTER_VERTICAL"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.FILL_VERTICAL, to="FILL_VERTICAL"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.CENTER_HORIZONTAL, to="CENTER_HORIZONTAL"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.FILL_HORIZONTAL, to="FILL_HORIZONTAL"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.CENTER, to="CENTER"), @android.view.ViewDebug.IntToString(from=android.view.Gravity.FILL, to="FILL")}) public int gravity;
}
}
"""
val expectedApi =
"""
package android.app {
public static class ActionBar {
field public int gravity;
}
}
"""
check(signatureSource = source, api = expectedApi)
}
}