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.cli.compatibility 18 19 import com.android.tools.metalava.cli.common.BaseOptionGroupTest 20 import com.android.tools.metalava.cli.common.SignatureBasedApi 21 import com.android.tools.metalava.model.api.surface.ApiVariantType 22 import com.android.tools.metalava.model.visitors.ApiType 23 import com.android.tools.metalava.testing.signature 24 import com.android.tools.metalava.testing.source 25 import com.google.common.truth.Truth.assertThat 26 import org.junit.Assert.assertThrows 27 import org.junit.Test 28 29 val COMPATIBILITY_CHECK_OPTIONS_HELP = 30 """ 31 Compatibility Checks: 32 33 Options controlling which, if any, compatibility checks are performed against a previously released API. 34 35 --check-compatibility:api:released <file> Check compatibility of the previously released API. 36 37 When multiple files are provided any files that are a delta on another file 38 must come after the other file, e.g. if `system` is a delta on `public` 39 then `public` must come first, then `system`. Or, in other words, they must 40 be provided in order from the narrowest API to the widest API. 41 --check-compatibility:removed:released <file> 42 Check compatibility of the previously released but since removed APIs. 43 44 When multiple files are provided any files that are a delta on another file 45 must come after the other file, e.g. if `system` is a delta on `public` 46 then `public` must come first, then `system`. Or, in other words, they must 47 be provided in order from the narrowest API to the widest API. 48 --api-compat-annotation <annotation> Specify an annotation important for API compatibility. 49 50 Adding/removing this annotation will be considered an incompatible change. 51 The fully qualified name of the annotation should be passed. 52 --error-message:compatibility:released <message> 53 If set, this is output when errors are detected in 54 --check-compatibility:api:released or 55 --check-compatibility:removed:released. 56 --baseline:compatibility:released <file> An optional baseline file that contains a list of known compatibility 57 issues which should be ignored. If this does not exist and 58 --update-baseline:compatibility:released is not specified then it will be 59 created and populated with all the known compatibility issues. 60 --update-baseline:compatibility:released <file> 61 An optional file into which a list of the latest compatibility issues found 62 will be written. If --baseline:compatibility:released is specified then any 63 issues listed in there will be copied into this file; that minimizes the 64 amount of churn in the baseline file when updating by not removing legacy 65 issues that have been fixed. If --delete-empty-baselines is specified and 66 this baseline is empty then the file will be deleted. 67 """ 68 .trimIndent() 69 70 class CompatibilityCheckOptionsTest : 71 BaseOptionGroupTest<CompatibilityCheckOptions>( 72 COMPATIBILITY_CHECK_OPTIONS_HELP, 73 ) { 74 createOptionsnull75 override fun createOptions(): CompatibilityCheckOptions = CompatibilityCheckOptions() 76 77 @Test 78 fun `check compatibility api released`() { 79 val file = 80 signature("released.txt", "// Signature format: 2.0\n").createFile(temporaryFolder.root) 81 runTest(ARG_CHECK_COMPATIBILITY_API_RELEASED, file.path) { 82 assertThat(options.compatibilityChecks) 83 .isEqualTo( 84 listOf( 85 CompatibilityCheckOptions.CheckRequest( 86 previouslyReleasedApi = SignatureBasedApi.fromFiles(listOf(file)), 87 apiType = ApiType.PUBLIC_API, 88 ), 89 ) 90 ) 91 } 92 } 93 94 @Test check compatibility api released multiple filesnull95 fun `check compatibility api released multiple files`() { 96 val file1 = 97 signature("released1.txt", "// Signature format: 2.0\n") 98 .createFile(temporaryFolder.root) 99 val file2 = 100 signature("released2.txt", "// Signature format: 2.0\n") 101 .createFile(temporaryFolder.root) 102 runTest( 103 ARG_CHECK_COMPATIBILITY_API_RELEASED, 104 file1.path, 105 ARG_CHECK_COMPATIBILITY_API_RELEASED, 106 file2.path, 107 ) { 108 assertThat(options.compatibilityChecks) 109 .isEqualTo( 110 listOf( 111 CompatibilityCheckOptions.CheckRequest( 112 previouslyReleasedApi = 113 SignatureBasedApi.fromFiles(listOf(file1, file2)), 114 apiType = ApiType.PUBLIC_API, 115 ), 116 ) 117 ) 118 } 119 } 120 121 @Test check compatibility removed api releasednull122 fun `check compatibility removed api released`() { 123 val file = 124 signature("removed.txt", "// Signature format: 2.0\n").createFile(temporaryFolder.root) 125 runTest(ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED, file.path) { 126 assertThat(options.compatibilityChecks) 127 .isEqualTo( 128 listOf( 129 CompatibilityCheckOptions.CheckRequest( 130 previouslyReleasedApi = 131 SignatureBasedApi.fromFiles( 132 listOf(file), 133 apiVariantType = ApiVariantType.REMOVED, 134 ), 135 apiType = ApiType.REMOVED, 136 ), 137 ) 138 ) 139 } 140 } 141 142 /** 143 * Create a fake jar file. It is ok that it is not actually a jar file as its contents are not 144 * read. 145 */ fakeJarnull146 private fun fakeJar() = source("some.jar", "PK...").createFile(temporaryFolder.root) 147 148 @Test 149 fun `check compatibility api released from jar`() { 150 val jarFile = fakeJar() 151 val exception = 152 assertThrows(IllegalStateException::class.java) { 153 runTest(ARG_CHECK_COMPATIBILITY_API_RELEASED, jarFile.path) { 154 options.compatibilityChecks 155 } 156 } 157 158 assertThat(exception.message) 159 .isEqualTo( 160 "$ARG_CHECK_COMPATIBILITY_API_RELEASED: Can no longer check compatibility against jar files like $jarFile please use equivalent signature files" 161 ) 162 } 163 164 @Test check compatibility api released mixture of signature and jarnull165 fun `check compatibility api released mixture of signature and jar`() { 166 val jarFile = fakeJar() 167 val signatureFile = 168 signature("removed.txt", "// Signature format: 2.0\n").createFile(temporaryFolder.root) 169 170 val exception = 171 assertThrows(IllegalStateException::class.java) { 172 runTest( 173 ARG_CHECK_COMPATIBILITY_API_RELEASED, 174 jarFile.path, 175 ARG_CHECK_COMPATIBILITY_API_RELEASED, 176 signatureFile.path, 177 ) { 178 options.compatibilityChecks 179 } 180 } 181 182 assertThat(exception.message) 183 .isEqualTo( 184 "$ARG_CHECK_COMPATIBILITY_API_RELEASED: Can no longer check compatibility against jar files like $jarFile please use equivalent signature files" 185 ) 186 } 187 188 @Test check compatibility api removed does not support jar filenull189 fun `check compatibility api removed does not support jar file`() { 190 val jarFile = fakeJar() 191 192 val exception = 193 assertThrows(IllegalStateException::class.java) { 194 runTest(ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED, jarFile.path) { 195 options.compatibilityChecks 196 } 197 } 198 assertThat(exception.message) 199 .isEqualTo( 200 "$ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED: Can no longer check compatibility against jar files like $jarFile please use equivalent signature files" 201 ) 202 } 203 204 @Test api compat annotations multiple valuesnull205 fun `api compat annotations multiple values`() { 206 runTest( 207 ARG_API_COMPAT_ANNOTATION, 208 "com.example.MyAnnotation", 209 ARG_API_COMPAT_ANNOTATION, 210 "com.example.MyOtherAnnotation", 211 ) { 212 assertThat(options.apiCompatAnnotations) 213 .containsExactly("com.example.MyAnnotation", "com.example.MyOtherAnnotation") 214 } 215 } 216 } 217