blob: feb9f206166b3079224b2c253ad1dcd1ae5fd710 [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.stub
import com.android.tools.metalava.ARG_API_CLASS_RESOLUTION
import com.android.tools.metalava.ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS
import com.android.tools.metalava.ARG_KOTLIN_STUBS
import com.android.tools.metalava.deprecatedForSdkSource
import com.android.tools.metalava.lint.DefaultLintErrorMessage
import com.android.tools.metalava.model.text.FileFormat
import com.android.tools.metalava.supportParameterName
import com.android.tools.metalava.systemApiSource
import com.android.tools.metalava.testApiSource
import com.android.tools.metalava.testing.java
import com.android.tools.metalava.testing.kotlin
import org.junit.Test
@SuppressWarnings("ALL")
class StubsTest : AbstractStubsTest() {
// TODO: test fields that need initialization
// TODO: test @DocOnly handling
@Test
fun `Generate stubs for fields with initial values`() {
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("ALL")
public class Foo {
private int hidden = 1;
int hidden2 = 2;
/** @hide */
int hidden3 = 3;
protected int field00; // No value
public static final boolean field01 = true;
public static final int field02 = 42;
public static final long field03 = 42L;
public static final short field04 = 5;
public static final byte field05 = 5;
public static final char field06 = 'c';
public static final float field07 = 98.5f;
public static final double field08 = 98.5;
public static final String field09 = "String with \"escapes\" and \u00a9...";
public static final double field10 = Double.NaN;
public static final double field11 = Double.POSITIVE_INFINITY;
public static final boolean field12;
public static final byte field13;
public static final char field14;
public static final short field15;
public static final int field16;
public static final long field17;
public static final float field18;
public static final double field19;
public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
public static final char HEX_INPUT = 61184;
}
"""
)
),
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
public static final java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
public static final char HEX_INPUT = 61184; // 0xef00 '\uef00'
protected int field00;
public static final boolean field01 = true;
public static final int field02 = 42; // 0x2a
public static final long field03 = 42L; // 0x2aL
public static final short field04 = 5; // 0x5
public static final byte field05 = 5; // 0x5
public static final char field06 = 99; // 0x0063 'c'
public static final float field07 = 98.5f;
public static final double field08 = 98.5;
public static final java.lang.String field09 = "String with \"escapes\" and \u00a9...";
public static final double field10 = (0.0/0.0);
public static final double field11 = (1.0/0.0);
public static final boolean field12;
static { field12 = false; }
public static final byte field13;
static { field13 = 0; }
public static final char field14;
static { field14 = 0; }
public static final short field15;
static { field15 = 0; }
public static final int field16;
static { field16 = 0; }
public static final long field17;
static { field17 = 0; }
public static final float field18;
static { field18 = 0; }
public static final double field19;
static { field19 = 0; }
}
""",
checkTextStubEquivalence = true
)
}
@Test
fun `Generate stubs for various modifier scenarios`() {
// Include as many modifiers as possible to see which ones are included
// in the signature files, and the expected sorting order.
// Note that the signature files treat "deprecated" as a fake modifier.
// Note also how the "protected" modifier on the interface method gets
// promoted to public.
checkStubs(
warnings = null,
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("ALL")
public abstract class Foo {
/** @deprecated */ @Deprecated private static final long field1 = 5;
/** @deprecated */ @Deprecated private static volatile long field2 = 5;
/** @deprecated */ @Deprecated public static strictfp final synchronized void method1() { }
/** @deprecated */ @Deprecated public static final synchronized native void method2();
/** @deprecated */ @Deprecated protected static final class Inner1 { }
/** @deprecated */ @Deprecated protected static abstract class Inner2 { }
/** @deprecated */ @Deprecated protected interface Inner3 {
protected default void method3() { }
static void method4() { }
}
}
"""
)
),
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
/** @deprecated */
@Deprecated
public static final synchronized void method1() { throw new RuntimeException("Stub!"); }
/** @deprecated */
@Deprecated
public static final synchronized native void method2();
/** @deprecated */
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
protected static final class Inner1 {
@Deprecated
protected Inner1() { throw new RuntimeException("Stub!"); }
}
/** @deprecated */
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
protected abstract static class Inner2 {
@Deprecated
protected Inner2() { throw new RuntimeException("Stub!"); }
}
/** @deprecated */
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
protected static interface Inner3 {
@Deprecated
public default void method3() { throw new RuntimeException("Stub!"); }
@Deprecated
public static void method4() { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@Test
fun `Check throws list`() {
// Make sure we format a throws list
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
import java.io.IOException;
@SuppressWarnings("RedundantThrows")
public abstract class AbstractCursor {
@Override protected void finalize1() throws Throwable { }
@Override protected void finalize2() throws IOException, IllegalArgumentException { }
}
"""
)
),
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class AbstractCursor {
public AbstractCursor() { throw new RuntimeException("Stub!"); }
protected void finalize1() throws java.lang.Throwable { throw new RuntimeException("Stub!"); }
protected void finalize2() throws java.io.IOException, java.lang.IllegalArgumentException { throw new RuntimeException("Stub!"); }
}
""",
checkTextStubEquivalence = true
)
}
@Test
fun `Test final instance fields`() {
// Instance fields in a class must be initialized
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("all")
public class InstanceFieldTest {
public static final class WindowLayout {
public WindowLayout(int width, int height, int gravity) {
this.width = width;
this.height = height;
this.gravity = gravity;
}
public final int width;
public final int height;
public final int gravity;
}
}
"""
)
),
warnings = "",
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class InstanceFieldTest {
public InstanceFieldTest() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static final class WindowLayout {
public WindowLayout(int width, int height, int gravity) { throw new RuntimeException("Stub!"); }
public final int gravity;
{ gravity = 0; }
public final int height;
{ height = 0; }
public final int width;
{ width = 0; }
}
}
"""
)
}
@Test
fun `Check overridden method added for complex hierarchy`() {
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
public final class A extends C implements B<String> {
@Override public void method2() { }
}
"""
),
java(
"""
package test.pkg;
public interface B<T> {
void method1(T arg1);
}
"""
),
java(
"""
package test.pkg;
public abstract class C extends D {
public abstract void method2();
}
"""
),
java(
"""
package test.pkg;
public abstract class D implements B<String> {
@Override public void method1(String arg1) { }
}
"""
)
),
stubFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public final class A extends test.pkg.C implements test.pkg.B<java.lang.String> {
public A() { throw new RuntimeException("Stub!"); }
public void method2() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public interface B<T> {
public void method1(T arg1);
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class C extends test.pkg.D {
public C() { throw new RuntimeException("Stub!"); }
public abstract void method2();
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class D implements test.pkg.B<java.lang.String> {
public D() { throw new RuntimeException("Stub!"); }
public void method1(java.lang.String arg1) { throw new RuntimeException("Stub!"); }
}
"""
)
),
checkTextStubEquivalence = true
)
}
@Test
fun `Preserve file header comments`() {
checkStubs(
sourceFiles =
arrayOf(
java(
"""
/*
My header 1
*/
/*
My header 2
*/
// My third comment
package test.pkg;
public class HeaderComments {
}
"""
)
),
source =
"""
/*
My header 1
*/
/*
My header 2
*/
// My third comment
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class HeaderComments {
public HeaderComments() { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Parameter Names in Java`() {
// Java code which explicitly specifies parameter names: make sure stub uses
// parameter name
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
import androidx.annotation.ParameterName;
public class Foo {
public void foo(int javaParameter1, @ParameterName("publicParameterName") int javaParameter2) {
}
}
"""
),
supportParameterName
),
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
public void foo(int javaParameter1, int publicParameterName) { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `DocOnly members should be omitted`() {
// When marked @doconly don't include in stubs or signature files
// unless specifically asked for (which we do when generating docs-stubs).
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("JavaDoc")
public class Outer {
/** @doconly Some docs here */
public class MyClass1 {
public int myField;
}
public class MyClass2 {
/** @doconly Some docs here */
public int myField;
/** @doconly Some docs here */
public int myMethod() { return 0; }
}
}
"""
)
),
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Outer {
public Outer() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass2 {
public MyClass2() { throw new RuntimeException("Stub!"); }
}
}
""",
api =
"""
package test.pkg {
public class Outer {
ctor public Outer();
}
public class Outer.MyClass2 {
ctor public Outer.MyClass2();
}
}
"""
)
}
@Test
fun `DocOnly members should be included when requested`() {
// When marked @doconly don't include in stubs or signature files
// unless specifically asked for (which we do when generating docs).
checkStubs(
docStubs = true,
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("JavaDoc")
public class Outer {
/** @doconly Some docs here */
public class MyClass1 {
public int myField;
}
public class MyClass2 {
/** @doconly Some docs here */
public int myField;
/** @doconly Some docs here */
public int myMethod() { return 0; }
}
}
"""
)
),
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Outer {
public Outer() { throw new RuntimeException("Stub!"); }
/** @doconly Some docs here */
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass1 {
public MyClass1() { throw new RuntimeException("Stub!"); }
public int myField;
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass2 {
public MyClass2() { throw new RuntimeException("Stub!"); }
/** @doconly Some docs here */
public int myMethod() { throw new RuntimeException("Stub!"); }
/** @doconly Some docs here */
public int myField;
}
}
"""
)
}
@Test
fun `Picking super class throwables`() {
// Like previous test, but without compatibility mode: ensures that we
// use super classes of filtered throwables
checkStubs(
format = FileFormat.V3,
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@SuppressWarnings({"RedundantThrows", "WeakerAccess"})
public class Generics {
public class MyClass<X, Y extends Number> extends HiddenParent<X, Y> implements PublicInterface<X, Y> {
}
class HiddenParent<M, N extends Number> extends PublicParent<M, N> {
public Map<M, Map<N, String>> createMap(List<M> list) throws MyThrowable {
return null;
}
protected List<M> foo() {
return null;
}
}
class MyThrowable extends IOException {
}
public abstract class PublicParent<A, B extends Number> {
protected abstract List<A> foo();
}
public interface PublicInterface<A, B> {
Map<A, Map<B, String>> createMap(List<A> list) throws IOException;
}
}
"""
)
),
warnings = "",
api =
"""
// Signature format: 3.0
package test.pkg {
public class Generics {
ctor public Generics();
}
public class Generics.MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> {
ctor public Generics.MyClass();
method public java.util.Map<X!,java.util.Map<Y!,java.lang.String!>!>! createMap(java.util.List<X!>!) throws java.io.IOException;
method protected java.util.List<X!>! foo();
}
public static interface Generics.PublicInterface<A, B> {
method public java.util.Map<A!,java.util.Map<B!,java.lang.String!>!>! createMap(java.util.List<A!>!) throws java.io.IOException;
}
public abstract class Generics.PublicParent<A, B extends java.lang.Number> {
ctor public Generics.PublicParent();
method protected abstract java.util.List<A!>! foo();
}
}
""",
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Generics {
public Generics() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> {
public MyClass() { throw new RuntimeException("Stub!"); }
protected java.util.List<X> foo() { throw new RuntimeException("Stub!"); }
public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X> list) throws java.io.IOException { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface PublicInterface<A, B> {
public java.util.Map<A,java.util.Map<B,java.lang.String>> createMap(java.util.List<A> list) throws java.io.IOException;
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class PublicParent<A, B extends java.lang.Number> {
public PublicParent() { throw new RuntimeException("Stub!"); }
protected abstract java.util.List<A> foo();
}
}
"""
)
}
@Test
fun `Rewriting implements class references`() {
// Checks some more subtle bugs around generics type variable renaming
checkStubs(
format = FileFormat.V2,
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
import java.util.Collection;
import java.util.Set;
@SuppressWarnings("all")
public class ConcurrentHashMap<K, V> {
public abstract static class KeySetView<K, V> extends CollectionView<K, V, K>
implements Set<K>, java.io.Serializable {
}
abstract static class CollectionView<K, V, E>
implements Collection<E>, java.io.Serializable {
public final Object[] toArray() { return null; }
public final <T> T[] toArray(T[] a) {
return null;
}
@Override
public int size() {
return 0;
}
}
}
"""
)
),
warnings = "",
api =
"""
package test.pkg {
public class ConcurrentHashMap<K, V> {
ctor public ConcurrentHashMap();
}
public abstract static class ConcurrentHashMap.KeySetView<K, V> implements java.util.Collection<K> java.io.Serializable java.util.Set<K> {
ctor public ConcurrentHashMap.KeySetView();
method public int size();
method public final Object[] toArray();
method public final <T> T[] toArray(T[]);
}
}
""",
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class ConcurrentHashMap<K, V> {
public ConcurrentHashMap() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class KeySetView<K, V> implements java.util.Collection<K>, java.io.Serializable, java.util.Set<K> {
public KeySetView() { throw new RuntimeException("Stub!"); }
public int size() { throw new RuntimeException("Stub!"); }
public final java.lang.Object[] toArray() { throw new RuntimeException("Stub!"); }
public final <T> T[] toArray(T[] a) { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@Test
fun `Arrays in type arguments`() {
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
public class Generics2 {
public class FloatArrayEvaluator implements TypeEvaluator<float[]> {
}
@SuppressWarnings("WeakerAccess")
public interface TypeEvaluator<T> {
}
}
"""
)
),
warnings = "",
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Generics2 {
public Generics2() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class FloatArrayEvaluator implements test.pkg.Generics2.TypeEvaluator<float[]> {
public FloatArrayEvaluator() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface TypeEvaluator<T> {
}
}
""",
checkTextStubEquivalence = true
)
}
@Test
fun `Overriding protected methods`() {
// Checks a scenario where the stubs were missing overrides
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("all")
public class Layouts {
public static class View {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
}
public static abstract class ViewGroup extends View {
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
}
public static class Toolbar extends ViewGroup {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
}
"""
)
),
warnings = "",
api =
"""
package test.pkg {
public class Layouts {
ctor public Layouts();
}
public static class Layouts.Toolbar extends test.pkg.Layouts.ViewGroup {
ctor public Layouts.Toolbar();
}
public static class Layouts.View {
ctor public Layouts.View();
method protected void onLayout(boolean, int, int, int, int);
}
public abstract static class Layouts.ViewGroup extends test.pkg.Layouts.View {
ctor public Layouts.ViewGroup();
method protected abstract void onLayout(boolean, int, int, int, int);
}
}
""",
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Layouts {
public Layouts() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class Toolbar extends test.pkg.Layouts.ViewGroup {
public Toolbar() { throw new RuntimeException("Stub!"); }
protected void onLayout(boolean changed, int l, int t, int r, int b) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class View {
public View() { throw new RuntimeException("Stub!"); }
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class ViewGroup extends test.pkg.Layouts.View {
public ViewGroup() { throw new RuntimeException("Stub!"); }
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
}
}
"""
)
}
@Test
fun `Missing overridden method`() {
// Another special case where overridden methods were missing
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
import java.util.Collection;
import java.util.Set;
@SuppressWarnings("all")
public class SpanTest {
public interface CharSequence {
}
public interface Spanned extends CharSequence {
public int nextSpanTransition(int start, int limit, Class type);
}
public interface Spannable extends Spanned {
}
public class SpannableString extends SpannableStringInternal implements CharSequence, Spannable {
}
/* package */ abstract class SpannableStringInternal {
public int nextSpanTransition(int start, int limit, Class kind) {
return 0;
}
}
}
"""
)
),
warnings = "",
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class SpanTest {
public SpanTest() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface CharSequence {
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface Spannable extends test.pkg.SpanTest.Spanned {
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class SpannableString implements test.pkg.SpanTest.CharSequence, test.pkg.SpanTest.Spannable {
public SpannableString() { throw new RuntimeException("Stub!"); }
public int nextSpanTransition(int start, int limit, java.lang.Class kind) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface Spanned extends test.pkg.SpanTest.CharSequence {
public int nextSpanTransition(int start, int limit, java.lang.Class type);
}
}
"""
)
}
@Test
fun `Skip type variables in casts`() {
// When generating casts in super constructor calls, use raw types
checkStubs(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("all")
public class Properties {
public abstract class Property<T, V> {
public Property(Class<V> type, String name) {
}
public Property(Class<V> type, String name, String name2) { // force casts in super
}
}
public abstract class IntProperty<T> extends Property<T, Integer> {
public IntProperty(String name) {
super(Integer.class, name);
}
}
}
"""
)
),
warnings = "",
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Properties {
public Properties() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class IntProperty<T> extends test.pkg.Properties.Property<T,java.lang.Integer> {
public IntProperty(java.lang.String name) { super((java.lang.Class)null, (java.lang.String)null); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class Property<T, V> {
public Property(java.lang.Class<V> type, java.lang.String name) { throw new RuntimeException("Stub!"); }
public Property(java.lang.Class<V> type, java.lang.String name, java.lang.String name2) { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@Test
fun `Generate stubs with --exclude-documentation-from-stubs`() {
checkStubs(
extraArguments = arrayOf(ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS),
sourceFiles =
arrayOf(
java(
"""
/*
* This is the copyright header.
*/
package test.pkg;
import java.util.List;
/** This is the documentation for the class */
public class Foo {
/** My field doc */
protected static final String field = "a\nb\n\"test\"";
/**
* Method documentation.
* @see List
*/
protected static void onCreate(List<String> parameter1) {
// This is not in the stub
System.out.println(parameter1);
}
}
"""
)
),
// Excludes javadoc because of ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS:
source =
"""
/*
* This is the copyright header.
*/
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
protected static void onCreate(java.util.List<java.lang.String> parameter1) { throw new RuntimeException("Stub!"); }
protected static final java.lang.String field = "a\nb\n\"test\"";
}
"""
)
}
@Test
fun `Generate documentation stubs with --exclude-documentation-from-stubs`() {
checkStubs(
extraArguments = arrayOf(ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS),
sourceFiles =
arrayOf(
java(
"""
/*
* This is the copyright header.
*/
package test.pkg;
import java.util.List;
/** This is the documentation for the class */
public class Foo {
/** My field doc */
protected static final String field = "a\nb\n\"test\"";
/**
* Method documentation.
* @see List
*/
protected static void onCreate(List<String> parameter1) {
// This is not in the stub
System.out.println(parameter1);
}
}
"""
)
),
docStubs = true,
// Includes javadoc despite ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS, because of docStubs:
source =
"""
/*
* This is the copyright header.
*/
package test.pkg;
import java.util.List;
/** This is the documentation for the class */
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
/**
* Method documentation.
* @see java.util.List
*/
protected static void onCreate(java.util.List<java.lang.String> parameter1) { throw new RuntimeException("Stub!"); }
/** My field doc */
protected static final java.lang.String field = "a\nb\n\"test\"";
}
"""
)
}
@Test
fun `Regression test for 116777737`() {
// Regression test for 116777737: Stub generation broken for Bouncycastle
// """
// It appears as though metalava does not handle the case where:
// 1) class Alpha extends Beta<Orange>.
// 2) class Beta<T> extends Charlie<T>.
// 3) class Beta is hidden.
//
// It should result in a stub where Alpha extends Charlie<Orange> but
// instead results in a stub where Alpha extends Charlie<T>, so the
// type substitution of Orange for T is lost.
// """
check(
expectedIssues =
"src/test/pkg/Alpha.java:2: warning: Public class test.pkg.Alpha stripped of unavailable superclass test.pkg.Beta [HiddenSuperclass]",
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
public class Orange {
private Orange() { }
}
"""
),
java(
"""
package test.pkg;
public class Alpha extends Beta<Orange> {
private Alpha() { }
}
"""
),
java(
"""
package test.pkg;
/** @hide */
public class Beta<T> extends Charlie<T> {
private Beta() { }
}
"""
),
java(
"""
package test.pkg;
public class Charlie<T> {
private Charlie() { }
}
"""
)
),
api =
"""
package test.pkg {
public class Alpha extends test.pkg.Charlie<test.pkg.Orange> {
}
public class Charlie<T> {
}
public class Orange {
}
}
""",
stubFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Orange {
Orange() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Alpha extends test.pkg.Charlie<test.pkg.Orange> {
Alpha() { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Basic Kotlin stubs`() {
check(
extraArguments = arrayOf(ARG_KOTLIN_STUBS),
sourceFiles =
arrayOf(
kotlin(
"""
/* My file header */
// Another comment
@file:JvmName("Driver")
package test.pkg
/** My class doc */
class Kotlin(
val property1: String = "Default Value",
arg2: Int
) : Parent() {
override fun method() = "Hello World"
/** My method doc */
fun otherMethod(ok: Boolean, times: Int) {
}
/** property doc */
var property2: String? = null
/** @hide */
var hiddenProperty: String? = "hidden"
private var someField = 42
@JvmField
var someField2 = 42
}
/** Parent class doc */
open class Parent {
open fun method(): String? = null
open fun method2(value1: Boolean, value2: Boolean?): String? = null
open fun method3(value1: Int?, value2: Int): Int = null
}
"""
),
kotlin(
"""
package test.pkg
open class ExtendableClass<T>
"""
)
),
stubFiles =
arrayOf(
kotlin(
"""
/* My file header */
// Another comment
package test.pkg
/** My class doc */
@file:Suppress("ALL")
class Kotlin : test.pkg.Parent() {
open fun Kotlin(open property1: java.lang.String!, open arg2: int): test.pkg.Kotlin! = error("Stub!")
open fun method(): java.lang.String = error("Stub!")
/** My method doc */
open fun otherMethod(open ok: boolean, open times: int): void = error("Stub!")
}
"""
),
kotlin(
"""
package test.pkg
@file:Suppress("ALL")
open class ExtendableClass<T> {
open fun ExtendableClass(): test.pkg.ExtendableClass<T!>! = error("Stub!")
}
"""
)
)
)
}
@Test
fun `NaN constants`() {
check(
checkCompilation = true,
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
public class MyClass {
public static final float floatNaN = 0.0f / 0.0f;
public static final double doubleNaN = 0.0d / 0.0;
}
"""
)
),
stubFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass {
public MyClass() { throw new RuntimeException("Stub!"); }
public static final double doubleNaN = (0.0/0.0);
public static final float floatNaN = (0.0f/0.0f);
}
"""
)
)
)
}
@Test
fun `Translate DeprecatedForSdk to Deprecated`() {
// See b/144111352
check(
expectedIssues =
"""
src/test/pkg/PublicApi.java:30: error: Method test.pkg.PublicApi.method4(): Documentation contains `@deprecated` which implies this API is fully deprecated, not just @DeprecatedForSdk [DeprecationMismatch]
""",
expectedFail = DefaultLintErrorMessage,
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
import android.annotation.DeprecatedForSdk;
import android.annotation.DeprecatedForSdk.*;
public class PublicApi {
private PublicApi() { }
// Normal deprecation:
/** @deprecated My deprecation reason 1 */
@Deprecated
public static void method1() { }
// Deprecated in the SDK. No comment; make sure annotation comment
// shows up in the doc stubs.
@DeprecatedForSdk("My deprecation reason 2")
public static void method2() { }
// Deprecated in the SDK, and has comment: Make sure comments merged
// in the doc stubs.
/**
* My docs here.
* @return the value
*/
@DeprecatedForSdk("My deprecation reason 3")
public static void method3() { } // warn about missing annotation
// Already implicitly deprecated everywhere (because of @deprecated
// comment; complain if combined with @DeprecatedForSdk
/** @deprecated Something */
@DeprecatedForSdk("Something")
public static void method4() { }
// Test @DeprecatedForSdk with specific exemptions; none of these are
// the current public SDK so make sure it's deprecated there.
// A different test will check whath appens when generating the
// system API or test API.
@DeprecatedForSdk(value = "Explanation", allowIn = { SYSTEM_API, TEST_API })
public static void method5() { }
}
"""
)
.indented(),
deprecatedForSdkSource
),
api =
"""
package test.pkg {
public class PublicApi {
method @Deprecated public static void method1();
method @Deprecated public static void method2();
method @Deprecated public static void method3();
method @Deprecated public static void method4();
method @Deprecated public static void method5();
}
}
""",
stubFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class PublicApi {
PublicApi() { throw new RuntimeException("Stub!"); }
/** @deprecated My deprecation reason 1 */
@Deprecated
public static void method1() { throw new RuntimeException("Stub!"); }
/**
* @deprecated My deprecation reason 2
*/
@Deprecated
public static void method2() { throw new RuntimeException("Stub!"); }
/**
* My docs here.
* @deprecated My deprecation reason 3
* @return the value
*/
@Deprecated
public static void method3() { throw new RuntimeException("Stub!"); }
/** @deprecated Something */
@Deprecated
public static void method4() { throw new RuntimeException("Stub!"); }
/**
* @deprecated Explanation
*/
@Deprecated
public static void method5() { throw new RuntimeException("Stub!"); }
}
"""
)
),
docStubs = true
)
}
@Test
fun `Translate DeprecatedForSdk with API Filtering`() {
// See b/144111352.
// Remaining: don't include @deprecated in the docs for allowed platforms!
check(
showAnnotations = arrayOf("android.annotation.SystemApi"),
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.DeprecatedForSdk;
public class PublicApi2 {
private PublicApi2() {
}
// This method should be deprecated in the SDK but *not* here in
// the system API (this test runs with --show-annotations SystemApi)
@DeprecatedForSdk(value = "My deprecation reason 1", allowIn = {SystemApi.class, TestApi.class})
public static void method1() {
}
// Same as method 1 (here we're just using a different annotation
// initializer form to test we're handling both types): *not* deprecated.
/**
* My docs.
*/
@DeprecatedForSdk(value = "My deprecation reason 2", allowIn = SystemApi.class)
public static void method2() {
}
// Finally, this method *is* deprecated in the system API and should
// show up as such.
/**
* My docs.
*/
@DeprecatedForSdk(value = "My deprecation reason 3", allowIn = TestApi.class)
public static void method3() {
}
}
"""
)
.indented(),
// Include some Kotlin files too to make sure we correctly handle
// annotation lookup for Kotlin (which uses UAST instead of plain Java PSI
// behind the scenes), even if android.util.ArrayMap is really implemented in
// Java
kotlin(
"""
package android.util
import android.annotation.DeprecatedForSdk
import android.annotation.SystemApi;
import android.annotation.TestApi;
@DeprecatedForSdk(value = "Use androidx.collection.ArrayMap")
class ArrayMap
@DeprecatedForSdk(value = "Use androidx.collection.ArrayMap", allowIn = [SystemApi::class])
class SystemArrayMap
@DeprecatedForSdk("Use android.Manifest.permission.ACCESS_FINE_LOCATION instead")
const val FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"
"""
)
.indented(),
deprecatedForSdkSource,
systemApiSource,
testApiSource
),
stubFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class PublicApi2 {
PublicApi2() { throw new RuntimeException("Stub!"); }
public static void method1() { throw new RuntimeException("Stub!"); }
/**
* My docs.
*/
public static void method2() { throw new RuntimeException("Stub!"); }
/**
* My docs.
* @deprecated My deprecation reason 3
*/
@Deprecated
public static void method3() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package android.util;
/**
* @deprecated Use androidx.collection.ArrayMap
*/
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
public final class ArrayMap {
@Deprecated
public ArrayMap() { throw new RuntimeException("Stub!"); }
}
"""
),
// SystemArrayMap is like ArrayMap, but has allowedIn=SystemApi::class, so
// it should not be deprecated here in the system api stubs
java(
"""
package android.util;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public final class SystemArrayMap {
public SystemArrayMap() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package android.util;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public final class ArrayMapKt {
/**
* @deprecated Use android.Manifest.permission.ACCESS_FINE_LOCATION instead
*/
@Deprecated @androidx.annotation.NonNull public static final java.lang.String FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
}
"""
)
),
docStubs = true
)
}
@Test
fun `From-text stubs can be generated from signature files with conflicting class definitions`() {
check(
format = FileFormat.V2,
signatureSources =
arrayOf(
"""
// Signature format: 2.0
package test.pkg {
public class SystemClassExtendingPublicClass extends test.pkg.PublicClass {
ctor public SystemClassExtendingPublicClass();
method public void foo(int i);
}
public class PublicClass {
ctor public PublicClass();
}
}
""", // current.txt
"""
// Signature format: 2.0
package test.pkg {
public class SystemClass extends test.pkg.PublicClass {
ctor public SystemClass();
method public void bar();
}
public class SystemClassExtendingPublicClass extends test.pkg.SystemClass {
}
}
""", // system-current.txt
),
stubFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class PublicClass {
public PublicClass() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class SystemClass extends test.pkg.PublicClass {
public SystemClass() { throw new RuntimeException("Stub!"); }
public void bar() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class SystemClassExtendingPublicClass extends test.pkg.SystemClass {
public SystemClassExtendingPublicClass() { throw new RuntimeException("Stub!"); }
public void foo(int i) { throw new RuntimeException("Stub!"); }
}
"""
),
),
)
}
@Test
fun `Ensure that when generating stubs from signature files the constructors are setup correctly`() {
check(
format = FileFormat.V2,
signatureSources =
arrayOf(
"""
// Signature format: 2.0
package test.pkg {
public abstract class Parent {
ctor protected Parent(String);
}
public static class Child extends test.pkg.Parent {
ctor protected Child(String);
}
}
""",
),
stubFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class Parent {
protected Parent(java.lang.String arg1) { throw new RuntimeException("Stub!"); }
}
"""
),
// class test.pkg.Parent does not have a default constructor but has a
// constructor that takes a String argument as an input. Therefore, the
// constructor in the class test.pkg.Child must call the super constructor with
// a String argument to avoid a compiler error.
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class Child extends test.pkg.Parent {
protected Child(java.lang.String arg1) { super(null); throw new RuntimeException("Stub!"); }
}
"""
),
),
extraArguments =
arrayOf(
ARG_API_CLASS_RESOLUTION,
"api:classpath",
),
)
}
@Test
fun `Compilable stubs are not generated when inheriting class exists in jar passed via classpath`() {
check(
format = FileFormat.V2,
signatureSources =
arrayOf(
"""
// Signature format: 2.0
package java.text {
public abstract class Format implements java.lang.Cloneable java.io.Serializable {
ctor protected Format();
}
public static class Format.Field extends java.text.AttributedCharacterIterator.Attribute {
ctor protected Format.Field(String);
}
}
""",
),
stubFiles =
arrayOf(
// class java.text.AttributedCharacterIterator.Attribute is included in
// android.jar, which is passed as classpath in DriverTest. The class does not
// have a default constructor but has a constructor that takes a String argument
// as an input. Therefore, the constructor in the class java.text.Format.Field
// must call the super constructor with a String argument to avoid compile
// error.
java(
"""
package java.text;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class Format implements java.lang.Cloneable, java.io.Serializable {
protected Format() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class Field extends java.text.AttributedCharacterIterator.Attribute {
protected Field(java.lang.String arg1) { super(null); throw new RuntimeException("Stub!"); }
}
}
"""
),
),
extraArguments =
arrayOf(
ARG_API_CLASS_RESOLUTION,
"api:classpath",
),
)
}
@Test
fun `Type-use annotations are not included in stubs`() {
check(
sourceFiles =
arrayOf(
java(
"""
package test.pkg;
@java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE)
public @interface TypeAnnotation {}
"""
),
java(
"""
package test.pkg;
@java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD)
public @interface MethodAnnotation {}
"""
),
java(
"""
package test.pkg;
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE_USE})
public @interface MethodAndTypeAnnotation {}
"""
),
java(
"""
package test.pkg;
import java.util.List;
public class Foo {
@MethodAnnotation
@MethodAndTypeAnnotation
public @TypeAnnotation List<@TypeAnnotation String> foo() {}
}
"""
)
),
stubFiles =
arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
@test.pkg.MethodAndTypeAnnotation
@test.pkg.MethodAnnotation
public java.util.List<java.lang.String> foo() { throw new RuntimeException("Stub!"); }
}
"""
)
),
format = FileFormat.V2,
api =
"""
package test.pkg {
public class Foo {
ctor public Foo();
method @test.pkg.MethodAndTypeAnnotation @test.pkg.MethodAnnotation public java.util.List<java.lang.String> foo();
}
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE_USE}) public @interface MethodAndTypeAnnotation {
}
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface MethodAnnotation {
}
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public @interface TypeAnnotation {
}
}
"""
)
}
}