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.TypeItem
22 import com.android.tools.metalava.model.TypeNullability
23 import com.android.tools.metalava.testing.KnownSourceFiles
24 import com.google.common.truth.Truth.*
25 
26 class NullabilityCodebaseContext(
27     codebaseContext: BaseModelTest.CodebaseContext,
28     /**
29      * True if nullness information came from annotations, false if it came from kotlin null
30      * suffixes.
31      */
32     val nullabilityFromAnnotations: Boolean,
33 ) : BaseModelTest.CodebaseContext by codebaseContext
34 
35 /**
36  * Runs a test where it matters whether nullability is provided by annotations (which it is in
37  * [javaSource] and [annotatedSignature]) or kotlin null suffixes (which it is in [kotlinSource] and
38  * [kotlinNullsSignature]).
39  *
40  * Runs [test] for the nullability-through-annotations inputs with `true` as the boolean parameter,
41  * and runs [test] for the nullability-through-suffixes inputs with `false` as the boolean
42  * parameter.
43  */
runNullabilityTestnull44 internal fun BaseModelTest.runNullabilityTest(
45     javaSource: TestFile,
46     annotatedSignature: TestFile,
47     kotlinSource: TestFile,
48     kotlinNullsSignature: TestFile,
49     test: NullabilityCodebaseContext.() -> Unit
50 ) {
51     runCodebaseTest(
52         inputSet(
53             javaSource,
54             // Access nullability annotations which are not type use.
55             KnownSourceFiles.notTypeUseNullableSource,
56             KnownSourceFiles.notTypeUseNonNullSource,
57             // Libcore nullability are type use.
58             KnownSourceFiles.libcoreNullableSource,
59             KnownSourceFiles.libcoreNonNullSource,
60         ),
61         inputSet(annotatedSignature)
62     ) {
63         val context = NullabilityCodebaseContext(this, true)
64         context.test()
65     }
66 
67     runCodebaseTest(kotlinSource, kotlinNullsSignature) {
68         val context = NullabilityCodebaseContext(this, false)
69         context.test()
70     }
71 }
72 
73 /**
74  * Make sure that this [TypeItem] has [TypeNullability.NONNULL] and check to make sure that it has
75  * (or does not have depending on [expectAnnotation]) an [AnnotationItem.isNonNull] annotation.
76  *
77  * @param expectAnnotation `true` if an appropriate annotation is expected, `false` if it is not,
78  *   `null` disables the annotation check.
79  */
assertHasNonNullNullabilitynull80 internal fun TypeItem.assertHasNonNullNullability(
81     expectAnnotation: Boolean? = null,
82     message: String? = null,
83 ) {
84     assertWithMessage(message ?: "").that(modifiers.nullability).isEqualTo(TypeNullability.NONNULL)
85     val nullabilityAnnotations = modifiers.annotations.filter { it.isNullnessAnnotation() }
86     when (expectAnnotation) {
87         true -> assertThat(nullabilityAnnotations.single().isNonNull()).isTrue()
88         false -> assertThat(nullabilityAnnotations).isEmpty()
89         else -> {}
90     }
91 }
92 
93 /**
94  * Make sure that this [TypeItem] has [TypeNullability.NULLABLE] and check to make sure that it has
95  * (or does not have depending on [expectAnnotation]) an [AnnotationItem.isNullable] annotation.
96  *
97  * @param expectAnnotation `true` if an appropriate annotation is expected, `false` if it is not,
98  *     * `null` disables the annotation check.
99  */
assertHasNullableNullabilitynull100 internal fun TypeItem.assertHasNullableNullability(expectAnnotation: Boolean? = null) {
101     assertThat(modifiers.nullability).isEqualTo(TypeNullability.NULLABLE)
102     val nullabilityAnnotations = modifiers.annotations.filter { it.isNullnessAnnotation() }
103     when (expectAnnotation) {
104         true -> assertThat(nullabilityAnnotations.single().isNullable()).isTrue()
105         false -> assertThat(nullabilityAnnotations).isEmpty()
106         else -> {}
107     }
108 }
109 
110 /** Make sure that this [TypeItem] has [TypeNullability.PLATFORM]. */
assertHasPlatformNullabilitynull111 internal fun TypeItem.assertHasPlatformNullability() {
112     assertThat(modifiers.nullability).isEqualTo(TypeNullability.PLATFORM)
113 }
114 
115 /** Make sure that this [TypeItem] has [TypeNullability.UNDEFINED]. */
assertHasUndefinedNullabilitynull116 internal fun TypeItem.assertHasUndefinedNullability() {
117     assertThat(modifiers.nullability).isEqualTo(TypeNullability.UNDEFINED)
118 }
119