1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.model.testsuite
18 
19 import com.android.tools.lint.checks.infrastructure.TestFile
20 import com.android.tools.metalava.model.AnnotationItem
21 import com.android.tools.metalava.model.Codebase
22 import com.android.tools.metalava.model.TypeItem
23 import com.android.tools.metalava.model.TypeNullability
24 import com.android.tools.metalava.testing.KnownSourceFiles
25 import com.google.common.truth.Truth.*
26 
27 class NullabilityCodebaseContext(
28     codebaseContext: BaseModelTest.CodebaseContext<Codebase>,
29     /**
30      * True if nullness information came from annotations, false if it came from kotlin null
31      * suffixes.
32      */
33     val nullabilityFromAnnotations: Boolean,
34 ) : BaseModelTest.CodebaseContext<Codebase> by codebaseContext
35 
36 /**
37  * Runs a test where it matters whether nullability is provided by annotations (which it is in
38  * [javaSource] and [annotatedSignature]) or kotlin null suffixes (which it is in [kotlinSource] and
39  * [kotlinNullsSignature]).
40  *
41  * Runs [test] for the nullability-through-annotations inputs with `true` as the boolean parameter,
42  * and runs [test] for the nullability-through-suffixes inputs with `false` as the boolean
43  * parameter.
44  */
runNullabilityTestnull45 internal fun BaseModelTest.runNullabilityTest(
46     javaSource: TestFile,
47     annotatedSignature: TestFile,
48     kotlinSource: TestFile,
49     kotlinNullsSignature: TestFile,
50     test: NullabilityCodebaseContext.() -> Unit
51 ) {
52     runCodebaseTest(
53         inputSet(
54             javaSource,
55             // Access nullability annotations which are not type use.
56             KnownSourceFiles.notTypeUseNullableSource,
57             KnownSourceFiles.notTypeUseNonNullSource,
58             // Libcore nullability are type use.
59             KnownSourceFiles.libcoreNullableSource,
60             KnownSourceFiles.libcoreNonNullSource,
61         ),
62         inputSet(annotatedSignature)
63     ) {
64         val context = NullabilityCodebaseContext(this, true)
65         context.test()
66     }
67 
68     runCodebaseTest(kotlinSource, kotlinNullsSignature) {
69         val context = NullabilityCodebaseContext(this, false)
70         context.test()
71     }
72 }
73 
74 /**
75  * Make sure that this [TypeItem] has [TypeNullability.NONNULL] and check to make sure that it has
76  * (or does not have depending on [expectAnnotation]) an [AnnotationItem.isNonNull] annotation.
77  *
78  * @param expectAnnotation `true` if an appropriate annotation is expected, `false` if it is not,
79  *   `null` disables the annotation check.
80  */
assertHasNonNullNullabilitynull81 internal fun TypeItem.assertHasNonNullNullability(
82     expectAnnotation: Boolean? = null,
83     message: String? = null,
84 ) {
85     assertWithMessage(message ?: "")
86         .that(modifiers.nullability())
87         .isEqualTo(TypeNullability.NONNULL)
88     val nullabilityAnnotations = modifiers.annotations().filter { it.isNullnessAnnotation() }
89     when (expectAnnotation) {
90         true -> assertThat(nullabilityAnnotations.single().isNonNull()).isTrue()
91         false -> assertThat(nullabilityAnnotations).isEmpty()
92         else -> {}
93     }
94 }
95 
96 /**
97  * Make sure that this [TypeItem] has [TypeNullability.NULLABLE] and check to make sure that it has
98  * (or does not have depending on [expectAnnotation]) an [AnnotationItem.isNullable] annotation.
99  *
100  * @param expectAnnotation `true` if an appropriate annotation is expected, `false` if it is not,
101  *     * `null` disables the annotation check.
102  */
assertHasNullableNullabilitynull103 internal fun TypeItem.assertHasNullableNullability(expectAnnotation: Boolean? = null) {
104     assertThat(modifiers.nullability()).isEqualTo(TypeNullability.NULLABLE)
105     val nullabilityAnnotations = modifiers.annotations().filter { it.isNullnessAnnotation() }
106     when (expectAnnotation) {
107         true -> assertThat(nullabilityAnnotations.single().isNullable()).isTrue()
108         false -> assertThat(nullabilityAnnotations).isEmpty()
109         else -> {}
110     }
111 }
112 
113 /** Make sure that this [TypeItem] has [TypeNullability.PLATFORM]. */
assertHasPlatformNullabilitynull114 internal fun TypeItem.assertHasPlatformNullability() {
115     assertThat(modifiers.nullability()).isEqualTo(TypeNullability.PLATFORM)
116 }
117 
118 /** Make sure that this [TypeItem] has [TypeNullability.UNDEFINED]. */
assertHasUndefinedNullabilitynull119 internal fun TypeItem.assertHasUndefinedNullability() {
120     assertThat(modifiers.nullability()).isEqualTo(TypeNullability.UNDEFINED)
121 }
122