1 /* <lambda>null2 * Copyright (C) 2017 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 18 19 import com.android.SdkConstants 20 import com.android.SdkConstants.DOT_JAVA 21 import com.android.SdkConstants.DOT_KT 22 import com.android.ide.common.process.DefaultProcessExecutor 23 import com.android.ide.common.process.LoggedProcessOutputHandler 24 import com.android.ide.common.process.ProcessException 25 import com.android.ide.common.process.ProcessInfoBuilder 26 import com.android.tools.lint.LintCliClient 27 import com.android.tools.lint.UastEnvironment 28 import com.android.tools.lint.checks.ApiLookup 29 import com.android.tools.lint.checks.infrastructure.ClassName 30 import com.android.tools.lint.checks.infrastructure.LintDetectorTest 31 import com.android.tools.lint.checks.infrastructure.TestFile 32 import com.android.tools.lint.checks.infrastructure.TestFiles 33 import com.android.tools.lint.checks.infrastructure.TestFiles.java 34 import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin 35 import com.android.tools.lint.checks.infrastructure.stripComments 36 import com.android.tools.lint.client.api.LintClient 37 import com.android.tools.metalava.model.text.ApiFile 38 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS 39 import com.android.tools.metalava.model.defaultConfiguration 40 import com.android.tools.metalava.model.parseDocument 41 import com.android.utils.FileUtils 42 import com.android.utils.SdkUtils 43 import com.android.utils.StdLogger 44 import com.google.common.io.ByteStreams 45 import com.google.common.io.Closeables 46 import com.google.common.io.Files 47 import com.intellij.openapi.util.Disposer 48 import org.intellij.lang.annotations.Language 49 import org.junit.Assert.assertEquals 50 import org.junit.Assert.assertNotNull 51 import org.junit.Assert.assertTrue 52 import org.junit.Assert.fail 53 import org.junit.Before 54 import org.junit.Rule 55 import org.junit.rules.ErrorCollector 56 import org.junit.rules.TemporaryFolder 57 import java.io.ByteArrayOutputStream 58 import java.io.File 59 import java.io.FileNotFoundException 60 import java.io.PrintStream 61 import java.io.PrintWriter 62 import java.io.StringWriter 63 import java.net.URL 64 import kotlin.text.Charsets.UTF_8 65 66 const val CHECK_JDIFF = false 67 68 /** 69 * Marker class for stubs argument to [DriverTest.check] indicating that no 70 * stubs should be generated for a particular source file. 71 */ 72 const val NO_STUB = "" 73 74 abstract class DriverTest { 75 @get:Rule 76 var temporaryFolder = TemporaryFolder() 77 78 @get:Rule 79 val errorCollector = ErrorCollector() 80 81 @Before 82 fun setup() { 83 System.setProperty(ENV_VAR_METALAVA_TESTS_RUNNING, SdkConstants.VALUE_TRUE) 84 Disposer.setDebugMode(true) 85 } 86 87 protected fun createProject(vararg files: TestFile): File { 88 val dir = temporaryFolder.newFolder("project") 89 90 files 91 .map { it.createFile(dir) } 92 .forEach { assertNotNull(it) } 93 94 return dir 95 } 96 97 // Makes a note to fail the test, but still allows the test to complete before failing 98 protected fun addError(error: String) { 99 errorCollector.addError(Throwable(error)) 100 } 101 102 protected fun runDriver(vararg args: String, expectedFail: String = ""): String { 103 104 resetTicker() 105 106 // Capture the actual input and output from System.out/err and compare it 107 // to the output printed through the official writer; they should be the same, 108 // otherwise we have stray println's littered in the code! 109 val previousOut = System.out 110 val previousErr = System.err 111 try { 112 val output = TeeWriter(previousOut) 113 System.setOut(PrintStream(output)) 114 val error = TeeWriter(previousErr) 115 System.setErr(PrintStream(error)) 116 117 val sw = StringWriter() 118 val writer = PrintWriter(sw) 119 120 Disposer.setDebugMode(true) 121 122 if (run(arrayOf(*args), writer, writer)) { 123 assertTrue("Test expected to fail but didn't. Expected failure: $expectedFail", expectedFail.isEmpty()) 124 } else { 125 val actualFail = cleanupString(sw.toString(), null) 126 if (cleanupString(expectedFail, null).replace(".", "").trim() != 127 actualFail.replace(".", "").trim() 128 ) { 129 if (expectedFail == "Aborting: Found compatibility problems with --check-compatibility" && 130 actualFail.startsWith("Aborting: Found compatibility problems checking the ") 131 ) { 132 // Special case for compat checks; we don't want to force each one of them 133 // to pass in the right string (which may vary based on whether writing out 134 // the signature was passed at the same time 135 // ignore 136 } else { 137 assertEquals(expectedFail.trimIndent(), actualFail) 138 } 139 } 140 } 141 142 val stdout = output.toString(UTF_8.name()) 143 if (stdout.isNotEmpty()) { 144 addError("Unexpected write to stdout:\n $stdout") 145 } 146 val stderr = error.toString(UTF_8.name()) 147 if (stderr.isNotEmpty()) { 148 addError("Unexpected write to stderr:\n $stderr") 149 } 150 151 val printedOutput = sw.toString() 152 if (printedOutput.isNotEmpty() && printedOutput.trim().isEmpty()) { 153 fail("Printed newlines with nothing else") 154 } 155 156 UastEnvironment.ensureDisposed() 157 Disposer.assertIsEmpty(true) 158 159 return printedOutput 160 } finally { 161 System.setOut(previousOut) 162 System.setErr(previousErr) 163 } 164 } 165 166 // This is here so we can keep a record of what was printed, to make sure we 167 // don't have any unexpected printlns in the source that are left behind after 168 // debugging and pollute the production output 169 class TeeWriter(private val otherStream: PrintStream) : ByteArrayOutputStream() { 170 override fun write(b: ByteArray?, off: Int, len: Int) { 171 otherStream.write(b, off, len) 172 super.write(b, off, len) 173 } 174 175 override fun write(b: ByteArray?) { 176 otherStream.write(b) 177 super.write(b) 178 } 179 180 override fun write(b: Int) { 181 otherStream.write(b) 182 super.write(b) 183 } 184 } 185 186 private fun findKotlinStdlibPath(): List<String> { 187 val classPath: String = System.getProperty("java.class.path") 188 val paths = mutableListOf<String>() 189 for (path in classPath.split(':')) { 190 val file = File(path) 191 val name = file.name 192 if (name.startsWith("kotlin-stdlib") || 193 name.startsWith("kotlin-reflect") || 194 name.startsWith("kotlin-script-runtime") 195 ) { 196 paths.add(file.path) 197 } 198 } 199 if (paths.isEmpty()) { 200 error("Did not find kotlin-stdlib-jre8 in $PROGRAM_NAME classpath: $classPath") 201 } 202 return paths 203 } 204 205 protected fun getJdkPath(): String? { 206 val javaHome = System.getProperty("java.home") 207 if (javaHome != null) { 208 var javaHomeFile = File(javaHome) 209 if (File(javaHomeFile, "bin${File.separator}javac").exists()) { 210 return javaHome 211 } else if (javaHomeFile.name == "jre") { 212 javaHomeFile = javaHomeFile.parentFile 213 if (javaHomeFile != null && File(javaHomeFile, "bin${File.separator}javac").exists()) { 214 return javaHomeFile.path 215 } 216 } 217 } 218 return System.getenv("JAVA_HOME") 219 } 220 221 private fun <T> buildOptionalArgs(option: T?, converter: (T) -> Array<String>): Array<String> { 222 return if (option != null) { 223 converter(option) 224 } else { 225 emptyArray() 226 } 227 } 228 229 /** File conversion tasks */ 230 data class ConvertData( 231 val fromApi: String, 232 val outputFile: String, 233 val baseApi: String? = null, 234 val strip: Boolean = true, 235 val format: FileFormat = FileFormat.JDIFF 236 ) 237 238 protected fun check( 239 /** Any jars to add to the class path */ 240 classpath: Array<TestFile>? = null, 241 /** The API signature content (corresponds to --api) */ 242 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 243 api: String? = null, 244 /** The API signature content (corresponds to --api-xml) */ 245 // @Language("XML") https://youtrack.jetbrains.com/issue/KT-35859 246 apiXml: String? = null, 247 /** The removed API (corresponds to --removed-api) */ 248 removedApi: String? = null, 249 /** The removed dex API (corresponds to --removed-dex-api) */ 250 removedDexApi: String? = null, 251 /** The subtract api signature content (corresponds to --subtract-api) */ 252 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 253 subtractApi: String? = null, 254 /** Expected stubs (corresponds to --stubs) in order corresponding to [sourceFiles]. Use 255 * [NO_STUB] as a marker for source files that are not expected to generate stubs */ 256 // @Language("JAVA") https://youtrack.jetbrains.com/issue/KT-35859 257 stubs: Array<String> = emptyArray(), 258 /** Stub source file list generated */ 259 stubsSourceList: String? = null, 260 /** Doc Stub source file list generated */ 261 docStubsSourceList: String? = null, 262 /** Whether the stubs should be written as documentation stubs instead of plain stubs. Decides 263 * whether the stubs include @doconly elements, uses rewritten/migration annotations, etc */ 264 docStubs: Boolean = false, 265 /** Signature file format */ 266 format: FileFormat? = null, 267 /** Whether to run in doclava1 compat mode */ 268 compatibilityMode: Boolean = format == null || format == FileFormat.V1, 269 /** Whether to trim the output (leading/trailing whitespace removal) */ 270 trim: Boolean = true, 271 /** Whether to remove blank lines in the output (the signature file usually contains a lot of these) */ 272 stripBlankLines: Boolean = true, 273 /** All expected issues to be generated when analyzing these sources */ 274 expectedIssues: String? = "", 275 /** Expected [Severity.ERROR] issues to be generated when analyzing these sources */ 276 errorSeverityExpectedIssues: String? = null, 277 checkCompilation: Boolean = false, 278 /** Annotations to merge in (in .xml format) */ 279 // @Language("XML") https://youtrack.jetbrains.com/issue/KT-35859 280 mergeXmlAnnotations: String? = null, 281 /** Annotations to merge in (in .txt/.signature format) */ 282 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 283 mergeSignatureAnnotations: String? = null, 284 /** Qualifier annotations to merge in (in Java stub format) */ 285 // @Language("JAVA") https://youtrack.jetbrains.com/issue/KT-35859 286 mergeJavaStubAnnotations: String? = null, 287 /** Inclusion annotations to merge in (in Java stub format) */ 288 // @Language("JAVA") https://youtrack.jetbrains.com/issue/KT-35859 289 mergeInclusionAnnotations: String? = null, 290 /** Optional API signature files content to load **instead** of Java/Kotlin source files */ 291 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 292 signatureSources: Array<String> = emptyArray(), 293 /** 294 * An optional API signature file content to load **instead** of Java/Kotlin source files. 295 * This is added to [signatureSources]. This argument exists for backward compatibility. 296 */ 297 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 298 signatureSource: String? = null, 299 /** An optional API jar file content to load **instead** of Java/Kotlin source files */ 300 apiJar: File? = null, 301 /** An optional API signature to check the current API's compatibility with */ 302 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 303 checkCompatibilityApi: String? = null, 304 /** An optional API signature to check the last released API's compatibility with */ 305 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 306 checkCompatibilityApiReleased: String? = null, 307 /** An optional API signature to check the current removed API's compatibility with */ 308 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 309 checkCompatibilityRemovedApiCurrent: String? = null, 310 /** An optional API signature to check the last released removed API's compatibility with */ 311 // @Language("TEXT") 312 checkCompatibilityRemovedApiReleased: String? = null, 313 /** An optional API signature to compute nullness migration status from */ 314 allowCompatibleDifferences: Boolean = true, 315 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 316 migrateNullsApi: String? = null, 317 /** Show annotations (--show-annotation arguments) */ 318 showAnnotations: Array<String> = emptyArray(), 319 /** "Show for stub purposes" API annotation ([ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION]) */ 320 showForStubPurposesAnnotations: Array<String> = emptyArray(), 321 /** Hide annotations (--hide-annotation arguments) */ 322 hideAnnotations: Array<String> = emptyArray(), 323 /** Hide meta-annotations (--hide-meta-annotation arguments) */ 324 hideMetaAnnotations: Array<String> = emptyArray(), 325 /** If using [showAnnotations], whether to include unannotated */ 326 showUnannotated: Boolean = false, 327 /** Additional arguments to supply */ 328 extraArguments: Array<String> = emptyArray(), 329 /** Whether we should emit Kotlin-style null signatures */ 330 outputKotlinStyleNulls: Boolean = format != null && format.useKotlinStyleNulls(), 331 /** Whether we should interpret API files being read as having Kotlin-style nullness types */ 332 inputKotlinStyleNulls: Boolean = false, 333 /** Whether we should omit java.lang. etc from signature files */ 334 omitCommonPackages: Boolean = !compatibilityMode, 335 /** Expected output (stdout and stderr combined). If null, don't check. */ 336 expectedOutput: String? = null, 337 /** Expected fail message and state, if any */ 338 expectedFail: String? = null, 339 /** List of extra jar files to record annotation coverage from */ 340 coverageJars: Array<TestFile>? = null, 341 /** Optional manifest to load and associate with the codebase */ 342 // @Language("XML") https://youtrack.jetbrains.com/issue/KT-35859 343 manifest: String? = null, 344 /** Packages to pre-import (these will therefore NOT be included in emitted stubs, signature files etc */ 345 importedPackages: List<String> = emptyList(), 346 /** Packages to skip emitting signatures/stubs for even if public (typically used for unit tests 347 * referencing to classpath classes that aren't part of the definitions and shouldn't be part of the 348 * test output; e.g. a test may reference java.lang.Enum but we don't want to start reporting all the 349 * public APIs in the java.lang package just because it's indirectly referenced via the "enum" superclass 350 */ 351 skipEmitPackages: List<String> = listOf("java.lang", "java.util", "java.io"), 352 /** Whether we should include --showAnnotations=android.annotation.SystemApi */ 353 includeSystemApiAnnotations: Boolean = false, 354 /** Whether we should warn about super classes that are stripped because they are hidden */ 355 includeStrippedSuperclassWarnings: Boolean = false, 356 /** Apply level to XML */ 357 applyApiLevelsXml: String? = null, 358 /** Corresponds to SDK constants file broadcast_actions.txt */ 359 sdk_broadcast_actions: String? = null, 360 /** Corresponds to SDK constants file activity_actions.txt */ 361 sdk_activity_actions: String? = null, 362 /** Corresponds to SDK constants file service_actions.txt */ 363 sdk_service_actions: String? = null, 364 /** Corresponds to SDK constants file categories.txt */ 365 sdk_categories: String? = null, 366 /** Corresponds to SDK constants file features.txt */ 367 sdk_features: String? = null, 368 /** Corresponds to SDK constants file widgets.txt */ 369 sdk_widgets: String? = null, 370 /** Map from artifact id to artifact descriptor */ 371 artifacts: Map<String, String>? = null, 372 /** Extract annotations and check that the given packages contain the given extracted XML files */ 373 extractAnnotations: Map<String, String>? = null, 374 /** Creates the nullability annotations validator, and check that the report has the given lines (does not define files to be validated) */ 375 validateNullability: Set<String>? = null, 376 /** Enable nullability validation for the listed classes */ 377 validateNullabilityFromList: String? = null, 378 /** 379 * Whether to include source retention annotations in the stubs (in that case they do not 380 * go into the extracted annotations zip file) 381 */ 382 includeSourceRetentionAnnotations: Boolean = true, 383 /** 384 * Whether to include the signature version in signatures 385 */ 386 includeSignatureVersion: Boolean = false, 387 /** 388 * List of signature files to convert to JDiff XML and the 389 * expected XML output. 390 */ 391 convertToJDiff: List<ConvertData> = emptyList(), 392 /** 393 * Hook for performing additional initialization of the project 394 * directory 395 */ 396 projectSetup: ((File) -> Unit)? = null, 397 /** Content of the baseline file to use, if any */ 398 baseline: String? = null, 399 /** If non-null, we expect the baseline file to be updated to this. [baseline] must also be set. */ 400 updateBaseline: String? = null, 401 /** Merge instead of replacing the baseline */ 402 mergeBaseline: String? = null, 403 404 /** [ARG_BASELINE_API_LINT] */ 405 baselineApiLint: String? = null, 406 /** [ARG_UPDATE_BASELINE_API_LINT] */ 407 updateBaselineApiLint: String? = null, 408 409 /** [ARG_BASELINE_CHECK_COMPATIBILITY_RELEASED] */ 410 baselineCheckCompatibilityReleased: String? = null, 411 /** [ARG_UPDATE_BASELINE_CHECK_COMPATIBILITY_RELEASED] */ 412 updateBaselineCheckCompatibilityReleased: String? = null, 413 414 /** [ARG_ERROR_MESSAGE_API_LINT] */ 415 errorMessageApiLint: String? = null, 416 /** [ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_RELEASED] */ 417 errorMessageCheckCompatibilityReleased: String? = null, 418 /** [ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_CURRENT] */ 419 errorMessageCheckCompatibilityCurrent: String? = null, 420 421 /** 422 * If non null, enable API lint. If non-blank, a codebase where only new APIs not in the codebase 423 * are linted. 424 */ 425 // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859 426 apiLint: String? = null, 427 /** The source files to pass to the analyzer */ 428 sourceFiles: Array<TestFile> = emptyArray(), 429 /** [ARG_REPEAT_ERRORS_MAX] */ 430 repeatErrorsMax: Int = 0 431 ) { 432 // Ensure different API clients don't interfere with each other 433 try { 434 val method = ApiLookup::class.java.getDeclaredMethod("dispose") 435 method.isAccessible = true 436 method.invoke(null) 437 } catch (ignore: Throwable) { 438 ignore.printStackTrace() 439 } 440 441 // Ensure that lint infrastructure (for UAST) knows it's dealing with a test 442 LintCliClient(LintClient.CLIENT_UNIT_TESTS) 443 444 if (compatibilityMode && mergeXmlAnnotations != null) { 445 fail( 446 "Can't specify both compatibilityMode and mergeXmlAnnotations: there were no " + 447 "annotations output in doclava1" 448 ) 449 } 450 if (compatibilityMode && mergeSignatureAnnotations != null) { 451 fail( 452 "Can't specify both compatibilityMode and mergeSignatureAnnotations: there were no " + 453 "annotations output in doclava1" 454 ) 455 } 456 if (compatibilityMode && mergeJavaStubAnnotations != null) { 457 fail( 458 "Can't specify both compatibilityMode and mergeJavaStubAnnotations: there were no " + 459 "annotations output in doclava1" 460 ) 461 } 462 if (compatibilityMode && mergeInclusionAnnotations != null) { 463 fail( 464 "Can't specify both compatibilityMode and mergeInclusionAnnotations" 465 ) 466 } 467 defaultConfiguration.reset() 468 469 @Suppress("NAME_SHADOWING") 470 val expectedFail = expectedFail ?: if ((checkCompatibilityApi != null || 471 checkCompatibilityApiReleased != null || 472 checkCompatibilityRemovedApiCurrent != null || 473 checkCompatibilityRemovedApiReleased != null) && 474 (expectedIssues != null && expectedIssues.trim().isNotEmpty()) 475 ) { 476 "Aborting: Found compatibility problems with --check-compatibility" 477 } else { 478 "" 479 } 480 481 // Unit test which checks that a signature file is as expected 482 val androidJar = getPlatformFile("android.jar") 483 484 val project = createProject(*sourceFiles) 485 486 val sourcePathDir = File(project, "src") 487 if (!sourcePathDir.isDirectory) { 488 sourcePathDir.mkdirs() 489 } 490 491 var sourcePath = sourcePathDir.path 492 493 // Make it easy to configure a source path with more than one source root: src and src2 494 if (sourceFiles.any { it.targetPath.startsWith("src2") }) { 495 sourcePath = sourcePath + File.pathSeparator + sourcePath + "2" 496 } 497 498 val sourceList = 499 if (signatureSources.isNotEmpty() || signatureSource != null) { 500 sourcePathDir.mkdirs() 501 502 // if signatureSource is set, add it to signatureSources. 503 val sources = signatureSources.toMutableList() 504 signatureSource?. let { sources.add(it) } 505 506 var num = 0 507 val args = mutableListOf<String>() 508 sources.forEach { file -> 509 val signatureFile = File( 510 project, 511 "load-api${ if (++num == 1) "" else num.toString() }.txt" 512 ) 513 signatureFile.writeText(file.trimIndent()) 514 args.add(signatureFile.path) 515 } 516 if (!includeStrippedSuperclassWarnings) { 517 args.add(ARG_HIDE) 518 args.add("HiddenSuperclass") // Suppress warning #111 519 } 520 args.toTypedArray() 521 } else if (apiJar != null) { 522 sourcePathDir.mkdirs() 523 assert(sourceFiles.isEmpty()) { "Shouldn't combine sources with API jar file loads" } 524 arrayOf(apiJar.path) 525 } else { 526 sourceFiles.asSequence().map { File(project, it.targetPath).path }.toList().toTypedArray() 527 } 528 529 val classpathArgs: Array<String> = if (classpath != null) { 530 val classpathString = classpath 531 .map { it.createFile(project) } 532 .map { it.path } 533 .joinToString(separator = File.pathSeparator) { it } 534 535 arrayOf(ARG_CLASS_PATH, classpathString) 536 } else { 537 emptyArray() 538 } 539 540 val allReportedIssues = StringBuilder() 541 val errorSeverityReportedIssues = StringBuilder() 542 Reporter.rootFolder = project 543 Reporter.reportPrinter = { message, severity -> 544 val cleanedUpMessage = cleanupString(message, project).trim() 545 if (severity == Severity.ERROR) { 546 errorSeverityReportedIssues.append(cleanedUpMessage).append('\n') 547 } 548 allReportedIssues.append(cleanedUpMessage).append('\n') 549 } 550 551 val mergeAnnotationsArgs = if (mergeXmlAnnotations != null) { 552 val merged = File(project, "merged-annotations.xml") 553 merged.writeText(mergeXmlAnnotations.trimIndent()) 554 arrayOf(ARG_MERGE_QUALIFIER_ANNOTATIONS, merged.path) 555 } else { 556 emptyArray() 557 } 558 559 val signatureAnnotationsArgs = if (mergeSignatureAnnotations != null) { 560 val merged = File(project, "merged-annotations.txt") 561 merged.writeText(mergeSignatureAnnotations.trimIndent()) 562 arrayOf(ARG_MERGE_QUALIFIER_ANNOTATIONS, merged.path) 563 } else { 564 emptyArray() 565 } 566 567 val javaStubAnnotationsArgs = if (mergeJavaStubAnnotations != null) { 568 // We need to place the qualifier class into its proper package location 569 // to make the parsing machinery happy 570 val cls = ClassName(mergeJavaStubAnnotations) 571 val pkg = cls.packageName 572 val relative = pkg?.replace('.', File.separatorChar) ?: "." 573 val merged = File(project, "qualifier/$relative/${cls.className}.java") 574 merged.parentFile.mkdirs() 575 merged.writeText(mergeJavaStubAnnotations.trimIndent()) 576 arrayOf(ARG_MERGE_QUALIFIER_ANNOTATIONS, merged.path) 577 } else { 578 emptyArray() 579 } 580 581 val inclusionAnnotationsArgs = if (mergeInclusionAnnotations != null) { 582 val cls = ClassName(mergeInclusionAnnotations) 583 val pkg = cls.packageName 584 val relative = pkg?.replace('.', File.separatorChar) ?: "." 585 val merged = File(project, "inclusion/$relative/${cls.className}.java") 586 merged.parentFile?.mkdirs() 587 merged.writeText(mergeInclusionAnnotations.trimIndent()) 588 arrayOf(ARG_MERGE_INCLUSION_ANNOTATIONS, merged.path) 589 } else { 590 emptyArray() 591 } 592 593 val apiLintArgs = if (apiLint != null) { 594 if (apiLint.isBlank()) { 595 arrayOf(ARG_API_LINT) 596 } else { 597 val file = File(project, "prev-api-lint.txt") 598 file.writeText(apiLint.trimIndent()) 599 arrayOf(ARG_API_LINT, file.path) 600 } 601 } else { 602 emptyArray() 603 } 604 605 val checkCompatibilityApiFile = if (checkCompatibilityApi != null) { 606 val jar = File(checkCompatibilityApi) 607 if (jar.isFile) { 608 jar 609 } else { 610 val file = File(project, "current-api.txt") 611 file.writeText(checkCompatibilityApi.trimIndent()) 612 file 613 } 614 } else { 615 null 616 } 617 618 val checkCompatibilityApiReleasedFile = if (checkCompatibilityApiReleased != null) { 619 val jar = File(checkCompatibilityApiReleased) 620 if (jar.isFile) { 621 jar 622 } else { 623 val file = File(project, "released-api.txt") 624 file.writeText(checkCompatibilityApiReleased.trimIndent()) 625 file 626 } 627 } else { 628 null 629 } 630 631 val checkCompatibilityRemovedApiCurrentFile = if (checkCompatibilityRemovedApiCurrent != null) { 632 val jar = File(checkCompatibilityRemovedApiCurrent) 633 if (jar.isFile) { 634 jar 635 } else { 636 val file = File(project, "removed-current-api.txt") 637 file.writeText(checkCompatibilityRemovedApiCurrent.trimIndent()) 638 file 639 } 640 } else { 641 null 642 } 643 644 val checkCompatibilityRemovedApiReleasedFile = if (checkCompatibilityRemovedApiReleased != null) { 645 val jar = File(checkCompatibilityRemovedApiReleased) 646 if (jar.isFile) { 647 jar 648 } else { 649 val file = File(project, "removed-released-api.txt") 650 file.writeText(checkCompatibilityRemovedApiReleased.trimIndent()) 651 file 652 } 653 } else { 654 null 655 } 656 657 val migrateNullsApiFile = if (migrateNullsApi != null) { 658 val jar = File(migrateNullsApi) 659 if (jar.isFile) { 660 jar 661 } else { 662 val file = File(project, "stable-api.txt") 663 file.writeText(migrateNullsApi.trimIndent()) 664 file 665 } 666 } else { 667 null 668 } 669 670 val manifestFileArgs = if (manifest != null) { 671 val file = File(project, "manifest.xml") 672 file.writeText(manifest.trimIndent()) 673 arrayOf(ARG_MANIFEST, file.path) 674 } else { 675 emptyArray() 676 } 677 678 val migrateNullsArguments = if (migrateNullsApiFile != null) { 679 arrayOf(ARG_MIGRATE_NULLNESS, migrateNullsApiFile.path) 680 } else { 681 emptyArray() 682 } 683 684 val checkCompatibilityArguments = if (checkCompatibilityApiFile != null) { 685 val extra: Array<String> = if (allowCompatibleDifferences) { 686 arrayOf(ARG_ALLOW_COMPATIBLE_DIFFERENCES) 687 } else { 688 emptyArray() 689 } 690 arrayOf(ARG_CHECK_COMPATIBILITY_API_CURRENT, checkCompatibilityApiFile.path, *extra) 691 } else { 692 emptyArray() 693 } 694 695 val checkCompatibilityApiReleasedArguments = if (checkCompatibilityApiReleasedFile != null) { 696 arrayOf(ARG_CHECK_COMPATIBILITY_API_RELEASED, checkCompatibilityApiReleasedFile.path) 697 } else { 698 emptyArray() 699 } 700 701 val checkCompatibilityRemovedCurrentArguments = if (checkCompatibilityRemovedApiCurrentFile != null) { 702 val extra: Array<String> = if (allowCompatibleDifferences) { 703 arrayOf(ARG_ALLOW_COMPATIBLE_DIFFERENCES) 704 } else { 705 emptyArray() 706 } 707 arrayOf(ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT, checkCompatibilityRemovedApiCurrentFile.path, *extra) 708 } else { 709 emptyArray() 710 } 711 712 val checkCompatibilityRemovedReleasedArguments = if (checkCompatibilityRemovedApiReleasedFile != null) { 713 arrayOf(ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED, checkCompatibilityRemovedApiReleasedFile.path) 714 } else { 715 emptyArray() 716 } 717 718 val quiet = if (expectedOutput != null && !extraArguments.contains(ARG_VERBOSE)) { 719 // If comparing output, avoid noisy output such as the banner etc 720 arrayOf(ARG_QUIET) 721 } else { 722 emptyArray() 723 } 724 725 val coverageStats = if (coverageJars != null && coverageJars.isNotEmpty()) { 726 val sb = StringBuilder() 727 val root = File(project, "coverageJars") 728 root.mkdirs() 729 for (jar in coverageJars) { 730 if (sb.isNotEmpty()) { 731 sb.append(File.pathSeparator) 732 } 733 val file = jar.createFile(root) 734 sb.append(file.path) 735 } 736 arrayOf(ARG_ANNOTATION_COVERAGE_OF, sb.toString()) 737 } else { 738 emptyArray() 739 } 740 741 val showAnnotationArguments = if (showAnnotations.isNotEmpty() || includeSystemApiAnnotations) { 742 val args = mutableListOf<String>() 743 for (annotation in showAnnotations) { 744 args.add(ARG_SHOW_ANNOTATION) 745 args.add(annotation) 746 } 747 if (includeSystemApiAnnotations && !args.contains("android.annotation.SystemApi")) { 748 args.add(ARG_SHOW_ANNOTATION) 749 args.add("android.annotation.SystemApi") 750 } 751 if (includeSystemApiAnnotations && !args.contains("android.annotation.TestApi")) { 752 args.add(ARG_SHOW_ANNOTATION) 753 args.add("android.annotation.TestApi") 754 } 755 args.toTypedArray() 756 } else { 757 emptyArray() 758 } 759 760 val hideAnnotationArguments = if (hideAnnotations.isNotEmpty()) { 761 val args = mutableListOf<String>() 762 for (annotation in hideAnnotations) { 763 args.add(ARG_HIDE_ANNOTATION) 764 args.add(annotation) 765 } 766 args.toTypedArray() 767 } else { 768 emptyArray() 769 } 770 771 val showForStubPurposesAnnotationArguments = if (showForStubPurposesAnnotations.isNotEmpty()) { 772 val args = mutableListOf<String>() 773 for (annotation in showForStubPurposesAnnotations) { 774 args.add(ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION) 775 args.add(annotation) 776 } 777 args.toTypedArray() 778 } else { 779 emptyArray() 780 } 781 782 val hideMetaAnnotationArguments = if (hideMetaAnnotations.isNotEmpty()) { 783 val args = mutableListOf<String>() 784 for (annotation in hideMetaAnnotations) { 785 args.add(ARG_HIDE_META_ANNOTATION) 786 args.add(annotation) 787 } 788 args.toTypedArray() 789 } else { 790 emptyArray() 791 } 792 793 val showUnannotatedArgs = 794 if (showUnannotated) { 795 arrayOf(ARG_SHOW_UNANNOTATED) 796 } else { 797 emptyArray() 798 } 799 800 val includeSourceRetentionAnnotationArgs = 801 if (includeSourceRetentionAnnotations) { 802 arrayOf(ARG_INCLUDE_SOURCE_RETENTION) 803 } else { 804 emptyArray() 805 } 806 807 var removedApiFile: File? = null 808 val removedArgs = if (removedApi != null) { 809 removedApiFile = temporaryFolder.newFile("removed.txt") 810 arrayOf(ARG_REMOVED_API, removedApiFile.path) 811 } else { 812 emptyArray() 813 } 814 815 var removedDexApiFile: File? = null 816 val removedDexArgs = if (removedDexApi != null) { 817 removedDexApiFile = temporaryFolder.newFile("removed-dex.txt") 818 arrayOf(ARG_REMOVED_DEX_API, removedDexApiFile.path) 819 } else { 820 emptyArray() 821 } 822 823 var apiFile: File? = null 824 val apiArgs = if (api != null) { 825 apiFile = temporaryFolder.newFile("public-api.txt") 826 arrayOf(ARG_API, apiFile.path) 827 } else { 828 emptyArray() 829 } 830 831 var apiXmlFile: File? = null 832 val apiXmlArgs = if (apiXml != null) { 833 apiXmlFile = temporaryFolder.newFile("public-api-xml.txt") 834 arrayOf(ARG_XML_API, apiXmlFile.path) 835 } else { 836 emptyArray() 837 } 838 839 val subtractApiFile: File? 840 val subtractApiArgs = if (subtractApi != null) { 841 subtractApiFile = temporaryFolder.newFile("subtract-api.txt") 842 subtractApiFile.writeText(subtractApi.trimIndent()) 843 arrayOf(ARG_SUBTRACT_API, subtractApiFile.path) 844 } else { 845 emptyArray() 846 } 847 848 val convertFiles = mutableListOf<Options.ConvertFile>() 849 val convertArgs = if (convertToJDiff.isNotEmpty()) { 850 val args = mutableListOf<String>() 851 var index = 1 852 for (convert in convertToJDiff) { 853 val signature = convert.fromApi 854 val base = convert.baseApi 855 val convertSig = temporaryFolder.newFile("convert-signatures$index.txt") 856 convertSig.writeText(signature.trimIndent(), UTF_8) 857 val extension = convert.format.preferredExtension() 858 val output = temporaryFolder.newFile("convert-output$index$extension") 859 val baseFile = if (base != null) { 860 val baseFile = temporaryFolder.newFile("convert-signatures$index-base.txt") 861 baseFile.writeText(base.trimIndent(), UTF_8) 862 baseFile 863 } else { 864 null 865 } 866 convertFiles += Options.ConvertFile(convertSig, output, baseFile, 867 strip = true, outputFormat = convert.format) 868 index++ 869 870 if (baseFile != null) { 871 args += 872 when { 873 convert.format == FileFormat.V1 -> ARG_CONVERT_NEW_TO_V1 874 convert.format == FileFormat.V2 -> ARG_CONVERT_NEW_TO_V2 875 convert.strip -> "-new_api" 876 else -> ARG_CONVERT_NEW_TO_JDIFF 877 } 878 args += baseFile.path 879 } else { 880 args += 881 when { 882 convert.format == FileFormat.V1 -> ARG_CONVERT_TO_V1 883 convert.format == FileFormat.V2 -> ARG_CONVERT_TO_V2 884 convert.strip -> "-convert2xml" 885 else -> ARG_CONVERT_TO_JDIFF 886 } 887 } 888 args += convertSig.path 889 args += output.path 890 } 891 args.toTypedArray() 892 } else { 893 emptyArray() 894 } 895 896 var stubsDir: File? = null 897 val stubsArgs = if (stubs.isNotEmpty()) { 898 stubsDir = temporaryFolder.newFolder("stubs") 899 if (docStubs) { 900 arrayOf(ARG_DOC_STUBS, stubsDir.path) 901 } else { 902 arrayOf(ARG_STUBS, stubsDir.path) 903 } 904 } else { 905 emptyArray() 906 } 907 908 var stubsSourceListFile: File? = null 909 val stubsSourceListArgs = if (stubsSourceList != null) { 910 stubsSourceListFile = temporaryFolder.newFile("droiddoc-src-list") 911 arrayOf(ARG_STUBS_SOURCE_LIST, stubsSourceListFile.path) 912 } else { 913 emptyArray() 914 } 915 916 var docStubsSourceListFile: File? = null 917 val docStubsSourceListArgs = if (docStubsSourceList != null) { 918 docStubsSourceListFile = temporaryFolder.newFile("droiddoc-doc-src-list") 919 arrayOf(ARG_DOC_STUBS_SOURCE_LIST, docStubsSourceListFile.path) 920 } else { 921 emptyArray() 922 } 923 924 val applyApiLevelsXmlFile: File? 925 val applyApiLevelsXmlArgs = if (applyApiLevelsXml != null) { 926 ApiLookup::class.java.getDeclaredMethod("dispose").apply { isAccessible = true }.invoke(null) 927 applyApiLevelsXmlFile = temporaryFolder.newFile("api-versions.xml") 928 applyApiLevelsXmlFile?.writeText(applyApiLevelsXml.trimIndent()) 929 arrayOf(ARG_APPLY_API_LEVELS, applyApiLevelsXmlFile.path) 930 } else { 931 emptyArray() 932 } 933 934 fun buildBaselineArgs( 935 argBaseline: String, 936 argUpdateBaseline: String, 937 argMergeBaseline: String, 938 filename: String, 939 baselineContent: String?, 940 updateContent: String?, 941 merge: Boolean 942 ): Pair<Array<String>, File?> { 943 if (baselineContent != null) { 944 val baselineFile = temporaryFolder.newFile(filename) 945 baselineFile?.writeText(baselineContent.trimIndent()) 946 if (!(updateContent != null || merge)) { 947 return Pair(arrayOf(argBaseline, baselineFile.path), baselineFile) 948 } else { 949 return Pair(arrayOf(argBaseline, 950 baselineFile.path, 951 if (mergeBaseline != null) argMergeBaseline else argUpdateBaseline, 952 baselineFile.path), baselineFile) 953 } 954 } else { 955 return Pair(emptyArray(), null) 956 } 957 } 958 959 val (baselineArgs, baselineFile) = buildBaselineArgs( 960 ARG_BASELINE, ARG_UPDATE_BASELINE, ARG_MERGE_BASELINE, "baseline.txt", 961 baseline, updateBaseline, mergeBaseline != null 962 ) 963 val (baselineApiLintArgs, baselineApiLintFile) = buildBaselineArgs( 964 ARG_BASELINE_API_LINT, ARG_UPDATE_BASELINE_API_LINT, "", 965 "baseline-api-lint.txt", 966 baselineApiLint, updateBaselineApiLint, false 967 ) 968 val (baselineCheckCompatibilityReleasedArgs, baselineCheckCompatibilityReleasedFile) = buildBaselineArgs( 969 ARG_BASELINE_CHECK_COMPATIBILITY_RELEASED, ARG_UPDATE_BASELINE_CHECK_COMPATIBILITY_RELEASED, "", 970 "baseline-check-released.txt", 971 baselineCheckCompatibilityReleased, updateBaselineCheckCompatibilityReleased, false 972 ) 973 974 val importedPackageArgs = mutableListOf<String>() 975 importedPackages.forEach { 976 importedPackageArgs.add("--stub-import-packages") 977 importedPackageArgs.add(it) 978 } 979 980 val skipEmitPackagesArgs = mutableListOf<String>() 981 skipEmitPackages.forEach { 982 skipEmitPackagesArgs.add("--skip-emit-packages") 983 skipEmitPackagesArgs.add(it) 984 } 985 986 val kotlinPath = findKotlinStdlibPath() 987 val kotlinPathArgs = 988 if (kotlinPath.isNotEmpty() && 989 sourceList.asSequence().any { it.endsWith(DOT_KT) } 990 ) { 991 arrayOf(ARG_CLASS_PATH, kotlinPath.joinToString(separator = File.pathSeparator) { it }) 992 } else { 993 emptyArray() 994 } 995 996 val sdkFilesDir: File? 997 val sdkFilesArgs: Array<String> 998 if (sdk_broadcast_actions != null || 999 sdk_activity_actions != null || 1000 sdk_service_actions != null || 1001 sdk_categories != null || 1002 sdk_features != null || 1003 sdk_widgets != null 1004 ) { 1005 val dir = File(project, "sdk-files") 1006 sdkFilesArgs = arrayOf(ARG_SDK_VALUES, dir.path) 1007 sdkFilesDir = dir 1008 } else { 1009 sdkFilesArgs = emptyArray() 1010 sdkFilesDir = null 1011 } 1012 1013 val artifactArgs = if (artifacts != null) { 1014 val args = mutableListOf<String>() 1015 var index = 1 1016 for ((artifactId, signatures) in artifacts) { 1017 val signatureFile = temporaryFolder.newFile("signature-file-$index.txt") 1018 signatureFile.writeText(signatures.trimIndent()) 1019 index++ 1020 1021 args.add(ARG_REGISTER_ARTIFACT) 1022 args.add(signatureFile.path) 1023 args.add(artifactId) 1024 } 1025 args.toTypedArray() 1026 } else { 1027 emptyArray() 1028 } 1029 1030 val extractedAnnotationsZip: File? 1031 val extractAnnotationsArgs = if (extractAnnotations != null) { 1032 extractedAnnotationsZip = temporaryFolder.newFile("extracted-annotations.zip") 1033 arrayOf(ARG_EXTRACT_ANNOTATIONS, extractedAnnotationsZip.path) 1034 } else { 1035 extractedAnnotationsZip = null 1036 emptyArray() 1037 } 1038 1039 val validateNullabilityTxt: File? 1040 val validateNullabilityArgs = if (validateNullability != null) { 1041 validateNullabilityTxt = temporaryFolder.newFile("validate-nullability.txt") 1042 arrayOf( 1043 ARG_NULLABILITY_WARNINGS_TXT, validateNullabilityTxt.path, 1044 ARG_NULLABILITY_ERRORS_NON_FATAL // for testing, report on errors instead of throwing 1045 ) 1046 } else { 1047 validateNullabilityTxt = null 1048 emptyArray() 1049 } 1050 val validateNullablityFromListFile: File? 1051 val validateNullabilityFromListArgs = if (validateNullabilityFromList != null) { 1052 validateNullablityFromListFile = temporaryFolder.newFile("validate-nullability-classes.txt") 1053 validateNullablityFromListFile.writeText(validateNullabilityFromList) 1054 arrayOf( 1055 ARG_VALIDATE_NULLABILITY_FROM_LIST, validateNullablityFromListFile.path 1056 ) 1057 } else { 1058 emptyArray() 1059 } 1060 1061 val signatureFormatArgs = if (format != null) { 1062 arrayOf(format.outputFlag()) 1063 } else { 1064 emptyArray() 1065 } 1066 1067 val errorMessageApiLintArgs = buildOptionalArgs(errorMessageApiLint) { 1068 arrayOf(ARG_ERROR_MESSAGE_API_LINT, it) 1069 } 1070 val errorMessageCheckCompatibilityReleasedArgs = buildOptionalArgs(errorMessageCheckCompatibilityReleased) { 1071 arrayOf(ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_RELEASED, it) 1072 } 1073 val errorMessageCheckCompatibilityCurrentArgs = buildOptionalArgs(errorMessageCheckCompatibilityCurrent) { 1074 arrayOf(ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_CURRENT, it) 1075 } 1076 1077 val repeatErrorsMaxArgs = if (repeatErrorsMax > 0) { 1078 arrayOf(ARG_REPEAT_ERRORS_MAX, repeatErrorsMax.toString()) 1079 } else { 1080 emptyArray() 1081 } 1082 1083 // Run optional additional setup steps on the project directory 1084 projectSetup?.invoke(project) 1085 1086 val actualOutput = runDriver( 1087 ARG_NO_COLOR, 1088 ARG_NO_BANNER, 1089 1090 // Tell metalava where to store temp folder: place them under the 1091 // test root folder such that we clean up the output strings referencing 1092 // paths to the temp folder 1093 "--temp-folder", 1094 temporaryFolder.newFolder("temp").path, 1095 1096 // Annotation generation temporarily turned off by default while integrating with 1097 // SDK builds; tests need these 1098 ARG_INCLUDE_ANNOTATIONS, 1099 1100 ARG_SOURCE_PATH, 1101 sourcePath, 1102 ARG_CLASS_PATH, 1103 androidJar.path, 1104 *classpathArgs, 1105 *kotlinPathArgs, 1106 *removedArgs, 1107 *removedDexArgs, 1108 *apiArgs, 1109 *apiXmlArgs, 1110 *subtractApiArgs, 1111 *stubsArgs, 1112 *stubsSourceListArgs, 1113 *docStubsSourceListArgs, 1114 "$ARG_COMPAT_OUTPUT=${if (compatibilityMode) "yes" else "no"}", 1115 "$ARG_OUTPUT_KOTLIN_NULLS=${if (outputKotlinStyleNulls) "yes" else "no"}", 1116 "$ARG_INPUT_KOTLIN_NULLS=${if (inputKotlinStyleNulls) "yes" else "no"}", 1117 "$ARG_OMIT_COMMON_PACKAGES=${if (omitCommonPackages) "yes" else "no"}", 1118 "$ARG_INCLUDE_SIG_VERSION=${if (includeSignatureVersion) "yes" else "no"}", 1119 *coverageStats, 1120 *quiet, 1121 *mergeAnnotationsArgs, 1122 *signatureAnnotationsArgs, 1123 *javaStubAnnotationsArgs, 1124 *inclusionAnnotationsArgs, 1125 *migrateNullsArguments, 1126 *checkCompatibilityArguments, 1127 *checkCompatibilityApiReleasedArguments, 1128 *checkCompatibilityRemovedCurrentArguments, 1129 *checkCompatibilityRemovedReleasedArguments, 1130 *manifestFileArgs, 1131 *convertArgs, 1132 *applyApiLevelsXmlArgs, 1133 *baselineArgs, 1134 *baselineApiLintArgs, 1135 *baselineCheckCompatibilityReleasedArgs, 1136 *showAnnotationArguments, 1137 *hideAnnotationArguments, 1138 *hideMetaAnnotationArguments, 1139 *showForStubPurposesAnnotationArguments, 1140 *showUnannotatedArgs, 1141 *includeSourceRetentionAnnotationArgs, 1142 *apiLintArgs, 1143 *sdkFilesArgs, 1144 *importedPackageArgs.toTypedArray(), 1145 *skipEmitPackagesArgs.toTypedArray(), 1146 *artifactArgs, 1147 *extractAnnotationsArgs, 1148 *validateNullabilityArgs, 1149 *validateNullabilityFromListArgs, 1150 *signatureFormatArgs, 1151 *sourceList, 1152 *extraArguments, 1153 *errorMessageApiLintArgs, 1154 *errorMessageCheckCompatibilityReleasedArgs, 1155 *errorMessageCheckCompatibilityCurrentArgs, 1156 *repeatErrorsMaxArgs, 1157 expectedFail = expectedFail 1158 ) 1159 1160 if (expectedIssues != null) { 1161 assertEquals( 1162 expectedIssues.trimIndent().trim(), 1163 cleanupString(allReportedIssues.toString(), project) 1164 ) 1165 } 1166 if (errorSeverityExpectedIssues != null) { 1167 assertEquals( 1168 errorSeverityExpectedIssues.trimIndent().trim(), 1169 cleanupString(errorSeverityReportedIssues.toString(), project) 1170 ) 1171 } 1172 1173 if (expectedOutput != null) { 1174 assertEquals(expectedOutput.trimIndent().trim(), actualOutput.trim()) 1175 } 1176 1177 if (api != null && apiFile != null) { 1178 assertTrue("${apiFile.path} does not exist even though --api was used", apiFile.exists()) 1179 val actualText = readFile(apiFile, stripBlankLines, trim) 1180 assertEquals(stripComments(api, stripLineComments = false).trimIndent(), actualText) 1181 // Make sure we can read back the files we write 1182 ApiFile.parseApi(apiFile, options.outputKotlinStyleNulls) 1183 } 1184 1185 if (apiXml != null && apiXmlFile != null) { 1186 assertTrue( 1187 "${apiXmlFile.path} does not exist even though $ARG_XML_API was used", 1188 apiXmlFile.exists() 1189 ) 1190 val actualText = readFile(apiXmlFile, stripBlankLines, trim) 1191 assertEquals(stripComments(apiXml, stripLineComments = false).trimIndent(), actualText) 1192 // Make sure we can read back the files we write 1193 parseDocument(apiXmlFile.readText(UTF_8), false) 1194 } 1195 1196 fun checkBaseline(arg: String, baselineContent: String?, updateBaselineContent: String?, mergeBaselineContent: String?, file: File?) { 1197 if (file == null) { 1198 return 1199 } 1200 assertTrue( 1201 "${file.path} does not exist even though $arg was used", 1202 file.exists() 1203 ) 1204 val actualText = readFile(file, stripBlankLines, trim) 1205 1206 // Compare against: 1207 // If "merged baseline" is set, use it. 1208 // If "update baseline" is set, use it. 1209 // Otherwise, the original baseline. 1210 val sourceFile = mergeBaselineContent ?: updateBaselineContent ?: baselineContent ?: "" 1211 assertEquals(stripComments(sourceFile, stripLineComments = false).trimIndent(), actualText) 1212 } 1213 checkBaseline(ARG_BASELINE, baseline, updateBaseline, mergeBaseline, baselineFile) 1214 checkBaseline(ARG_BASELINE_API_LINT, baselineApiLint, updateBaselineApiLint, null, baselineApiLintFile) 1215 checkBaseline(ARG_BASELINE_CHECK_COMPATIBILITY_RELEASED, baselineCheckCompatibilityReleased, 1216 updateBaselineCheckCompatibilityReleased, null, baselineCheckCompatibilityReleasedFile 1217 ) 1218 1219 if (convertFiles.isNotEmpty()) { 1220 for (i in convertToJDiff.indices) { 1221 val expected = convertToJDiff[i].outputFile 1222 val converted = convertFiles[i].outputFile 1223 if (convertToJDiff[i].baseApi != null && 1224 compatibilityMode && 1225 actualOutput.contains("No API change detected, not generating diff")) { 1226 continue 1227 } 1228 assertTrue( 1229 "${converted.path} does not exist even though $ARG_CONVERT_TO_JDIFF was used", 1230 converted.exists() 1231 ) 1232 val actualText = readFile(converted, stripBlankLines, trim) 1233 if (actualText.contains("<api")) { 1234 parseDocument(actualText, false) 1235 } 1236 assertEquals(stripComments(expected, stripLineComments = false).trimIndent(), actualText) 1237 // Make sure we can read back the files we write 1238 } 1239 } 1240 1241 if (removedApi != null && removedApiFile != null) { 1242 assertTrue( 1243 "${removedApiFile.path} does not exist even though --removed-api was used", 1244 removedApiFile.exists() 1245 ) 1246 val actualText = readFile(removedApiFile, stripBlankLines, trim) 1247 assertEquals(stripComments(removedApi, stripLineComments = false).trimIndent(), actualText) 1248 // Make sure we can read back the files we write 1249 ApiFile.parseApi(removedApiFile, options.outputKotlinStyleNulls) 1250 } 1251 1252 if (removedDexApi != null && removedDexApiFile != null) { 1253 assertTrue( 1254 "${removedDexApiFile.path} does not exist even though --removed-dex-api was used", 1255 removedDexApiFile.exists() 1256 ) 1257 val actualText = readFile(removedDexApiFile, stripBlankLines, trim) 1258 assertEquals(stripComments(removedDexApi, stripLineComments = false).trimIndent(), actualText) 1259 } 1260 1261 if (sdk_broadcast_actions != null) { 1262 val actual = readFile(File(sdkFilesDir, "broadcast_actions.txt"), stripBlankLines, trim) 1263 assertEquals(sdk_broadcast_actions.trimIndent().trim(), actual.trim()) 1264 } 1265 1266 if (sdk_activity_actions != null) { 1267 val actual = readFile(File(sdkFilesDir, "activity_actions.txt"), stripBlankLines, trim) 1268 assertEquals(sdk_activity_actions.trimIndent().trim(), actual.trim()) 1269 } 1270 1271 if (sdk_service_actions != null) { 1272 val actual = readFile(File(sdkFilesDir, "service_actions.txt"), stripBlankLines, trim) 1273 assertEquals(sdk_service_actions.trimIndent().trim(), actual.trim()) 1274 } 1275 1276 if (sdk_categories != null) { 1277 val actual = readFile(File(sdkFilesDir, "categories.txt"), stripBlankLines, trim) 1278 assertEquals(sdk_categories.trimIndent().trim(), actual.trim()) 1279 } 1280 1281 if (sdk_features != null) { 1282 val actual = readFile(File(sdkFilesDir, "features.txt"), stripBlankLines, trim) 1283 assertEquals(sdk_features.trimIndent().trim(), actual.trim()) 1284 } 1285 1286 if (sdk_widgets != null) { 1287 val actual = readFile(File(sdkFilesDir, "widgets.txt"), stripBlankLines, trim) 1288 assertEquals(sdk_widgets.trimIndent().trim(), actual.trim()) 1289 } 1290 1291 if (extractAnnotations != null && extractedAnnotationsZip != null) { 1292 assertTrue( 1293 "Using --extract-annotations but $extractedAnnotationsZip was not created", 1294 extractedAnnotationsZip.isFile 1295 ) 1296 for ((pkg, xml) in extractAnnotations) { 1297 assertPackageXml(pkg, extractedAnnotationsZip, xml) 1298 } 1299 } 1300 1301 if (validateNullabilityTxt != null) { 1302 assertTrue( 1303 "Using $ARG_NULLABILITY_WARNINGS_TXT but $validateNullabilityTxt was not created", 1304 validateNullabilityTxt.isFile 1305 ) 1306 val actualReport = 1307 Files.asCharSource(validateNullabilityTxt, UTF_8).readLines().map(String::trim).toSet() 1308 assertEquals(validateNullability, actualReport) 1309 } 1310 1311 if (stubs.isNotEmpty() && stubsDir != null) { 1312 for (i in stubs.indices) { 1313 var stub = stubs[i].trimIndent() 1314 1315 var targetPath: String 1316 var stubFile: File 1317 if (stub.startsWith("[") && stub.contains("]")) { 1318 val pathEnd = stub.indexOf("]\n") 1319 targetPath = stub.substring(1, pathEnd) 1320 stubFile = File(stubsDir, targetPath) 1321 if (stubFile.isFile) { 1322 stub = stub.substring(pathEnd + 2) 1323 } 1324 } else { 1325 val sourceFile = sourceFiles[i] 1326 targetPath = if (sourceFile.targetPath.endsWith(DOT_KT)) { 1327 // Kotlin source stubs are rewritten as .java files for now 1328 sourceFile.targetPath.substring(0, sourceFile.targetPath.length - 3) + DOT_JAVA 1329 } else { 1330 sourceFile.targetPath 1331 } 1332 stubFile = File(stubsDir, targetPath.substring("src/".length)) 1333 } 1334 if (!stubFile.isFile) { 1335 if (stub.startsWith("[") && stub.contains("]")) { 1336 val pathEnd = stub.indexOf("]\n") 1337 val path = stub.substring(1, pathEnd) 1338 stubFile = File(stubsDir, path) 1339 if (stubFile.isFile) { 1340 stub = stub.substring(pathEnd + 2) 1341 } 1342 } 1343 } 1344 if (stubFile.exists()) { 1345 val actualText = readFile(stubFile, stripBlankLines, trim) 1346 assertEquals(stub, actualText) 1347 } else if (stub != NO_STUB) { 1348 /* Example: 1349 stubs = arrayOf( 1350 """ 1351 [test/visible/package-info.java] 1352 <html>My package docs</html> 1353 package test.visible; 1354 """, 1355 ... 1356 Here the stub will be read from $stubsDir/test/visible/package-info.java. 1357 */ 1358 throw FileNotFoundException( 1359 "Could not find generated stub for $targetPath; consider " + 1360 "setting target relative path in stub header as prefix surrounded by []" 1361 ) 1362 } 1363 } 1364 } 1365 1366 if (stubsSourceList != null && stubsSourceListFile != null) { 1367 assertTrue( 1368 "${stubsSourceListFile.path} does not exist even though --write-stubs-source-list was used", 1369 stubsSourceListFile.exists() 1370 ) 1371 val actualText = cleanupString(readFile(stubsSourceListFile, stripBlankLines, trim), project) 1372 // To make golden files look better put one entry per line instead of a single 1373 // space separated line 1374 .replace(' ', '\n') 1375 assertEquals(stripComments(stubsSourceList, stripLineComments = false).trimIndent(), actualText) 1376 } 1377 1378 if (docStubsSourceList != null && docStubsSourceListFile != null) { 1379 assertTrue( 1380 "${docStubsSourceListFile.path} does not exist even though --write-stubs-source-list was used", 1381 docStubsSourceListFile.exists() 1382 ) 1383 val actualText = cleanupString(readFile(docStubsSourceListFile, stripBlankLines, trim), project) 1384 // To make golden files look better put one entry per line instead of a single 1385 // space separated line 1386 .replace(' ', '\n') 1387 assertEquals(stripComments(docStubsSourceList, stripLineComments = false).trimIndent(), actualText) 1388 } 1389 1390 if (checkCompilation && stubsDir != null) { 1391 val generated = gatherSources(listOf(stubsDir)).asSequence().map { it.path }.toList().toTypedArray() 1392 1393 // Also need to include on the compile path annotation classes referenced in the stubs 1394 val extraAnnotationsDir = File("stub-annotations/src/main/java") 1395 if (!extraAnnotationsDir.isDirectory) { 1396 fail("Couldn't find $extraAnnotationsDir: Is the pwd set to the root of the metalava source code?") 1397 fail("Couldn't find $extraAnnotationsDir: Is the pwd set to the root of an Android source tree?") 1398 } 1399 val extraAnnotations = 1400 gatherSources(listOf(extraAnnotationsDir)).asSequence().map { it.path }.toList().toTypedArray() 1401 1402 if (!runCommand( 1403 "${getJdkPath()}/bin/javac", arrayOf( 1404 "-d", project.path, *generated, *extraAnnotations 1405 ) 1406 ) 1407 ) { 1408 fail("Couldn't compile stub file -- compilation problems") 1409 return 1410 } 1411 } 1412 1413 if (CHECK_JDIFF && apiXmlFile != null && convertToJDiff.isNotEmpty()) { 1414 // TODO: Parse the XML file with jdiff too 1415 } 1416 } 1417 1418 /** Checks that the given zip annotations file contains the given XML package contents */ 1419 private fun assertPackageXml(pkg: String, output: File, @Language("XML") expected: String) { 1420 assertNotNull(output) 1421 assertTrue(output.exists()) 1422 val url = URL( 1423 "jar:" + SdkUtils.fileToUrlString(output) + "!/" + pkg.replace('.', '/') + 1424 "/annotations.xml" 1425 ) 1426 val stream = url.openStream() 1427 try { 1428 val bytes = ByteStreams.toByteArray(stream) 1429 assertNotNull(bytes) 1430 val xml = String(bytes, UTF_8).replace("\r\n", "\n") 1431 assertEquals(expected.trimIndent().trim(), xml.trimIndent().trim()) 1432 } finally { 1433 Closeables.closeQuietly(stream) 1434 } 1435 } 1436 1437 /** Hides path prefixes from /tmp folders used by the testing infrastructure */ 1438 private fun cleanupString(string: String, project: File?, dropTestRoot: Boolean = false): String { 1439 var s = string 1440 1441 if (project != null) { 1442 s = s.replace(project.path, "TESTROOT") 1443 s = s.replace(project.canonicalPath, "TESTROOT") 1444 } 1445 1446 s = s.replace(temporaryFolder.root.path, "TESTROOT") 1447 1448 val tmp = System.getProperty("java.io.tmpdir") 1449 if (tmp != null) { 1450 s = s.replace(tmp, "TEST") 1451 } 1452 1453 s = s.trim() 1454 1455 if (dropTestRoot) { 1456 s = s.replace("TESTROOT/", "") 1457 } 1458 1459 return s 1460 } 1461 1462 private fun runCommand(executable: String, args: Array<String>): Boolean { 1463 try { 1464 val logger = StdLogger(StdLogger.Level.ERROR) 1465 val processExecutor = DefaultProcessExecutor(logger) 1466 val processInfo = ProcessInfoBuilder() 1467 .setExecutable(executable) 1468 .addArgs(args) 1469 .createProcess() 1470 1471 val processOutputHandler = LoggedProcessOutputHandler(logger) 1472 val result = processExecutor.execute(processInfo, processOutputHandler) 1473 1474 result.rethrowFailure().assertNormalExitValue() 1475 } catch (e: ProcessException) { 1476 fail("Failed to run $executable (${e.message}): not verifying this API on the old doclava engine") 1477 return false 1478 } 1479 return true 1480 } 1481 1482 companion object { 1483 const val API_LEVEL = 27 1484 1485 private val latestAndroidPlatform: String 1486 get() = "android-$API_LEVEL" 1487 1488 private val sdk: File 1489 get() = File( 1490 System.getenv("ANDROID_HOME") 1491 ?: error("You must set \$ANDROID_HOME before running tests") 1492 ) 1493 1494 fun getAndroidJar(apiLevel: Int): File? { 1495 val localFile = File("../../prebuilts/sdk/$apiLevel/public/android.jar") 1496 if (localFile.exists()) { 1497 return localFile 1498 } else { 1499 val androidJar = File("../../prebuilts/sdk/$apiLevel/android.jar") 1500 if (androidJar.exists()) { 1501 return androidJar 1502 } 1503 } 1504 return null 1505 } 1506 1507 fun getPlatformFile(path: String): File { 1508 return getAndroidJar(API_LEVEL) ?: run { 1509 val file = FileUtils.join(sdk, SdkConstants.FD_PLATFORMS, latestAndroidPlatform, path) 1510 if (!file.exists()) { 1511 throw IllegalArgumentException( 1512 "File \"$path\" not found in platform $latestAndroidPlatform" 1513 ) 1514 } 1515 file 1516 } 1517 } 1518 1519 fun java(to: String, @Language("JAVA") source: String): LintDetectorTest.TestFile { 1520 return TestFiles.java(to, source.trimIndent()) 1521 } 1522 1523 fun java(@Language("JAVA") source: String): LintDetectorTest.TestFile { 1524 return TestFiles.java(source.trimIndent()) 1525 } 1526 1527 fun kotlin(@Language("kotlin") source: String): LintDetectorTest.TestFile { 1528 return TestFiles.kotlin(source.trimIndent()) 1529 } 1530 1531 fun kotlin(to: String, @Language("kotlin") source: String): LintDetectorTest.TestFile { 1532 return TestFiles.kotlin(to, source.trimIndent()) 1533 } 1534 1535 private fun readFile(file: File, stripBlankLines: Boolean = false, trim: Boolean = false): String { 1536 var apiLines: List<String> = Files.asCharSource(file, UTF_8).readLines() 1537 if (stripBlankLines) { 1538 apiLines = apiLines.asSequence().filter { it.isNotBlank() }.toList() 1539 } 1540 var apiText = apiLines.joinToString(separator = "\n") { it } 1541 if (trim) { 1542 apiText = apiText.trim() 1543 } 1544 return apiText 1545 } 1546 } 1547 } 1548 1549 val intRangeAnnotationSource: TestFile = java( 1550 """ 1551 package android.annotation; 1552 import java.lang.annotation.*; 1553 import static java.lang.annotation.ElementType.*; 1554 import static java.lang.annotation.RetentionPolicy.SOURCE; 1555 @Retention(SOURCE) 1556 @Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE}) 1557 public @interface IntRange { 1558 long from() default Long.MIN_VALUE; 1559 long to() default Long.MAX_VALUE; 1560 } 1561 """ 1562 ).indented() 1563 1564 val intDefAnnotationSource: TestFile = java( 1565 """ 1566 package android.annotation; 1567 import java.lang.annotation.Retention; 1568 import java.lang.annotation.RetentionPolicy; 1569 import java.lang.annotation.Target; 1570 import static java.lang.annotation.ElementType.*; 1571 import static java.lang.annotation.RetentionPolicy.SOURCE; 1572 @Retention(SOURCE) 1573 @Target({ANNOTATION_TYPE}) 1574 public @interface IntDef { 1575 int[] value() default {}; 1576 boolean flag() default false; 1577 } 1578 """ 1579 ).indented() 1580 1581 val longDefAnnotationSource: TestFile = java( 1582 """ 1583 package android.annotation; 1584 import java.lang.annotation.Retention; 1585 import java.lang.annotation.RetentionPolicy; 1586 import java.lang.annotation.Target; 1587 import static java.lang.annotation.ElementType.*; 1588 import static java.lang.annotation.RetentionPolicy.SOURCE; 1589 @Retention(SOURCE) 1590 @Target({ANNOTATION_TYPE}) 1591 public @interface LongDef { 1592 long[] value() default {}; 1593 boolean flag() default false; 1594 } 1595 """ 1596 ).indented() 1597 1598 @Suppress("ConstantConditionIf") 1599 val nonNullSource: TestFile = java( 1600 """ 1601 package android.annotation; 1602 import java.lang.annotation.Retention; 1603 import java.lang.annotation.Target; 1604 1605 import static java.lang.annotation.ElementType.FIELD; 1606 import static java.lang.annotation.ElementType.METHOD; 1607 import static java.lang.annotation.ElementType.PARAMETER; 1608 import static java.lang.annotation.RetentionPolicy.SOURCE; 1609 /** 1610 * Denotes that a parameter, field or method return value can never be null. 1611 * @paramDoc This value must never be {@code null}. 1612 * @returnDoc This value will never be {@code null}. 1613 * @hide 1614 */ 1615 @SuppressWarnings({"WeakerAccess", "JavaDoc"}) 1616 @Retention(SOURCE) 1617 @Target({METHOD, PARAMETER, FIELD${if (SUPPORT_TYPE_USE_ANNOTATIONS) ", TYPE_USE" else ""}}) 1618 public @interface NonNull { 1619 } 1620 """ 1621 ).indented() 1622 1623 val libcoreNonNullSource: TestFile = java( 1624 """ 1625 package libcore.util; 1626 import static java.lang.annotation.ElementType.*; 1627 import static java.lang.annotation.RetentionPolicy.SOURCE; 1628 import java.lang.annotation.*; 1629 @Documented 1630 @Retention(SOURCE) 1631 @Target({TYPE_USE}) 1632 public @interface NonNull { 1633 } 1634 """ 1635 ).indented() 1636 1637 val libcoreNullFromTypeParamSource: TestFile = java( 1638 """ 1639 package libcore.util; 1640 import static java.lang.annotation.ElementType.*; 1641 import static java.lang.annotation.RetentionPolicy.SOURCE; 1642 import java.lang.annotation.*; 1643 @Documented 1644 @Retention(SOURCE) 1645 @Target({TYPE_USE}) 1646 public @interface NullFromTypeParam { 1647 } 1648 """ 1649 ).indented() 1650 1651 val libcoreNullableSource: TestFile = java( 1652 """ 1653 package libcore.util; 1654 import static java.lang.annotation.ElementType.*; 1655 import static java.lang.annotation.RetentionPolicy.SOURCE; 1656 import java.lang.annotation.*; 1657 @Documented 1658 @Retention(SOURCE) 1659 @Target({TYPE_USE}) 1660 public @interface Nullable { 1661 } 1662 """ 1663 ).indented() 1664 1665 val requiresPermissionSource: TestFile = java( 1666 """ 1667 package android.annotation; 1668 import java.lang.annotation.*; 1669 import static java.lang.annotation.ElementType.*; 1670 import static java.lang.annotation.RetentionPolicy.SOURCE; 1671 @Retention(SOURCE) 1672 @Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER}) 1673 public @interface RequiresPermission { 1674 String value() default ""; 1675 String[] allOf() default {}; 1676 String[] anyOf() default {}; 1677 boolean conditional() default false; 1678 @Target({FIELD, METHOD, PARAMETER}) 1679 @interface Read { 1680 RequiresPermission value() default @RequiresPermission; 1681 } 1682 @Target({FIELD, METHOD, PARAMETER}) 1683 @interface Write { 1684 RequiresPermission value() default @RequiresPermission; 1685 } 1686 } 1687 """ 1688 ).indented() 1689 1690 val requiresFeatureSource: TestFile = java( 1691 """ 1692 package android.annotation; 1693 import java.lang.annotation.*; 1694 import static java.lang.annotation.ElementType.*; 1695 import static java.lang.annotation.RetentionPolicy.SOURCE; 1696 @Retention(SOURCE) 1697 @Target({TYPE,FIELD,METHOD,CONSTRUCTOR}) 1698 public @interface RequiresFeature { 1699 String value(); 1700 } 1701 """ 1702 ).indented() 1703 1704 val requiresApiSource: TestFile = java( 1705 """ 1706 package androidx.annotation; 1707 import java.lang.annotation.*; 1708 import static java.lang.annotation.ElementType.*; 1709 import static java.lang.annotation.RetentionPolicy.SOURCE; 1710 @Retention(SOURCE) 1711 @Target({TYPE,FIELD,METHOD,CONSTRUCTOR}) 1712 public @interface RequiresApi { 1713 int value() default 1; 1714 int api() default 1; 1715 } 1716 """ 1717 ).indented() 1718 1719 val sdkConstantSource: TestFile = java( 1720 """ 1721 package android.annotation; 1722 import java.lang.annotation.*; 1723 @Target({ ElementType.FIELD }) 1724 @Retention(RetentionPolicy.SOURCE) 1725 public @interface SdkConstant { 1726 enum SdkConstantType { 1727 ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE 1728 } 1729 SdkConstantType value(); 1730 } 1731 """ 1732 ).indented() 1733 1734 val broadcastBehaviorSource: TestFile = java( 1735 """ 1736 package android.annotation; 1737 import java.lang.annotation.*; 1738 /** @hide */ 1739 @Target({ ElementType.FIELD }) 1740 @Retention(RetentionPolicy.SOURCE) 1741 public @interface BroadcastBehavior { 1742 boolean explicitOnly() default false; 1743 boolean registeredOnly() default false; 1744 boolean includeBackground() default false; 1745 boolean protectedBroadcast() default false; 1746 } 1747 """ 1748 ).indented() 1749 1750 @Suppress("ConstantConditionIf") 1751 val nullableSource: TestFile = java( 1752 """ 1753 package android.annotation; 1754 import java.lang.annotation.*; 1755 import static java.lang.annotation.ElementType.*; 1756 import static java.lang.annotation.RetentionPolicy.SOURCE; 1757 /** 1758 * Denotes that a parameter, field or method return value can be null. 1759 * @paramDoc This value may be {@code null}. 1760 * @returnDoc This value may be {@code null}. 1761 * @hide 1762 */ 1763 @SuppressWarnings({"WeakerAccess", "JavaDoc"}) 1764 @Retention(SOURCE) 1765 @Target({METHOD, PARAMETER, FIELD${if (SUPPORT_TYPE_USE_ANNOTATIONS) ", TYPE_USE" else ""}}) 1766 public @interface Nullable { 1767 } 1768 """ 1769 ).indented() 1770 1771 val androidxNonNullSource: TestFile = java( 1772 """ 1773 package androidx.annotation; 1774 import java.lang.annotation.*; 1775 import static java.lang.annotation.ElementType.*; 1776 import static java.lang.annotation.RetentionPolicy.SOURCE; 1777 @SuppressWarnings("WeakerAccess") 1778 @Retention(SOURCE) 1779 @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, TYPE_USE, ANNOTATION_TYPE, PACKAGE}) 1780 public @interface NonNull { 1781 } 1782 """ 1783 ).indented() 1784 1785 val androidxNullableSource: TestFile = java( 1786 """ 1787 package androidx.annotation; 1788 import java.lang.annotation.*; 1789 import static java.lang.annotation.ElementType.*; 1790 import static java.lang.annotation.RetentionPolicy.SOURCE; 1791 @SuppressWarnings("WeakerAccess") 1792 @Retention(SOURCE) 1793 @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, TYPE_USE, ANNOTATION_TYPE, PACKAGE}) 1794 public @interface Nullable { 1795 } 1796 """ 1797 ).indented() 1798 1799 val recentlyNonNullSource: TestFile = java( 1800 """ 1801 package androidx.annotation; 1802 import java.lang.annotation.*; 1803 import static java.lang.annotation.ElementType.*; 1804 import static java.lang.annotation.RetentionPolicy.SOURCE; 1805 @SuppressWarnings("WeakerAccess") 1806 @Retention(SOURCE) 1807 @Target({METHOD, PARAMETER, FIELD, TYPE_USE}) 1808 public @interface RecentlyNonNull { 1809 } 1810 """ 1811 ).indented() 1812 1813 val recentlyNullableSource: TestFile = java( 1814 """ 1815 package androidx.annotation; 1816 import java.lang.annotation.*; 1817 import static java.lang.annotation.ElementType.*; 1818 import static java.lang.annotation.RetentionPolicy.SOURCE; 1819 @SuppressWarnings("WeakerAccess") 1820 @Retention(SOURCE) 1821 @Target({METHOD, PARAMETER, FIELD, TYPE_USE}) 1822 public @interface RecentlyNullable { 1823 } 1824 """ 1825 ).indented() 1826 1827 val supportParameterName: TestFile = java( 1828 """ 1829 package androidx.annotation; 1830 import java.lang.annotation.*; 1831 import static java.lang.annotation.ElementType.*; 1832 import static java.lang.annotation.RetentionPolicy.SOURCE; 1833 @SuppressWarnings("WeakerAccess") 1834 @Retention(SOURCE) 1835 @Target({METHOD, PARAMETER, FIELD}) 1836 public @interface ParameterName { 1837 String value(); 1838 } 1839 """ 1840 ).indented() 1841 1842 val supportDefaultValue: TestFile = java( 1843 """ 1844 package androidx.annotation; 1845 import java.lang.annotation.*; 1846 import static java.lang.annotation.ElementType.*; 1847 import static java.lang.annotation.RetentionPolicy.SOURCE; 1848 @SuppressWarnings("WeakerAccess") 1849 @Retention(SOURCE) 1850 @Target({METHOD, PARAMETER, FIELD}) 1851 public @interface DefaultValue { 1852 String value(); 1853 } 1854 """ 1855 ).indented() 1856 1857 val uiThreadSource: TestFile = java( 1858 """ 1859 package androidx.annotation; 1860 import java.lang.annotation.*; 1861 import static java.lang.annotation.ElementType.*; 1862 import static java.lang.annotation.RetentionPolicy.SOURCE; 1863 /** 1864 * Denotes that the annotated method or constructor should only be called on the 1865 * UI thread. If the annotated element is a class, then all methods in the class 1866 * should be called on the UI thread. 1867 * @memberDoc This method must be called on the thread that originally created 1868 * this UI element. This is typically the main thread of your app. 1869 * @classDoc Methods in this class must be called on the thread that originally created 1870 * this UI element, unless otherwise noted. This is typically the 1871 * main thread of your app. * @hide 1872 */ 1873 @SuppressWarnings({"WeakerAccess", "JavaDoc"}) 1874 @Retention(SOURCE) 1875 @Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER}) 1876 public @interface UiThread { 1877 } 1878 """ 1879 ).indented() 1880 1881 val workerThreadSource: TestFile = java( 1882 """ 1883 package androidx.annotation; 1884 import java.lang.annotation.*; 1885 import static java.lang.annotation.ElementType.*; 1886 import static java.lang.annotation.RetentionPolicy.SOURCE; 1887 /** 1888 * @memberDoc This method may take several seconds to complete, so it should 1889 * only be called from a worker thread. 1890 * @classDoc Methods in this class may take several seconds to complete, so it should 1891 * only be called from a worker thread unless otherwise noted. 1892 * @hide 1893 */ 1894 @SuppressWarnings({"WeakerAccess", "JavaDoc"}) 1895 @Retention(SOURCE) 1896 @Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER}) 1897 public @interface WorkerThread { 1898 } 1899 """ 1900 ).indented() 1901 1902 val suppressLintSource: TestFile = java( 1903 """ 1904 package android.annotation; 1905 1906 import static java.lang.annotation.ElementType.*; 1907 import java.lang.annotation.*; 1908 @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) 1909 @Retention(RetentionPolicy.CLASS) 1910 public @interface SuppressLint { 1911 String[] value(); 1912 } 1913 """ 1914 ).indented() 1915 1916 val systemServiceSource: TestFile = java( 1917 """ 1918 package android.annotation; 1919 import static java.lang.annotation.ElementType.TYPE; 1920 import static java.lang.annotation.RetentionPolicy.SOURCE; 1921 import java.lang.annotation.*; 1922 @Retention(SOURCE) 1923 @Target(TYPE) 1924 public @interface SystemService { 1925 String value(); 1926 } 1927 """ 1928 ).indented() 1929 1930 val systemApiSource: TestFile = java( 1931 """ 1932 package android.annotation; 1933 import static java.lang.annotation.ElementType.*; 1934 import java.lang.annotation.*; 1935 @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) 1936 @Retention(RetentionPolicy.SOURCE) 1937 public @interface SystemApi { 1938 } 1939 """ 1940 ).indented() 1941 1942 val testApiSource: TestFile = java( 1943 """ 1944 package android.annotation; 1945 import static java.lang.annotation.ElementType.*; 1946 import java.lang.annotation.*; 1947 @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) 1948 @Retention(RetentionPolicy.SOURCE) 1949 public @interface TestApi { 1950 } 1951 """ 1952 ).indented() 1953 1954 val widgetSource: TestFile = java( 1955 """ 1956 package android.annotation; 1957 import java.lang.annotation.*; 1958 @Target({ ElementType.TYPE }) 1959 @Retention(RetentionPolicy.SOURCE) 1960 public @interface Widget { 1961 } 1962 """ 1963 ).indented() 1964 1965 val restrictToSource: TestFile = java( 1966 """ 1967 package androidx.annotation; 1968 import java.lang.annotation.*; 1969 import static java.lang.annotation.ElementType.*; 1970 import static java.lang.annotation.RetentionPolicy.*; 1971 @SuppressWarnings("WeakerAccess") 1972 @Retention(CLASS) 1973 @Target({ANNOTATION_TYPE, TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE}) 1974 public @interface RestrictTo { 1975 Scope[] value(); 1976 enum Scope { 1977 LIBRARY, 1978 LIBRARY_GROUP, 1979 /** @deprecated */ 1980 @Deprecated 1981 GROUP_ID, 1982 TESTS, 1983 SUBCLASSES, 1984 } 1985 } 1986 """ 1987 ).indented() 1988 1989 val visibleForTestingSource: TestFile = java( 1990 """ 1991 package androidx.annotation; 1992 import static java.lang.annotation.RetentionPolicy.CLASS; 1993 import java.lang.annotation.Retention; 1994 @Retention(CLASS) 1995 @SuppressWarnings("WeakerAccess") 1996 public @interface VisibleForTesting { 1997 int otherwise() default PRIVATE; 1998 int PRIVATE = 2; 1999 int PACKAGE_PRIVATE = 3; 2000 int PROTECTED = 4; 2001 int NONE = 5; 2002 } 2003 """ 2004 ).indented() 2005 2006 val columnSource: TestFile = java( 2007 """ 2008 package android.provider; 2009 2010 import static java.lang.annotation.ElementType.FIELD; 2011 import static java.lang.annotation.RetentionPolicy.RUNTIME; 2012 2013 import android.content.ContentProvider; 2014 import android.content.ContentValues; 2015 import android.database.Cursor; 2016 2017 import java.lang.annotation.Documented; 2018 import java.lang.annotation.Retention; 2019 import java.lang.annotation.Target; 2020 2021 @Documented 2022 @Retention(RUNTIME) 2023 @Target({FIELD}) 2024 public @interface Column { 2025 int value(); 2026 boolean readOnly() default false; 2027 } 2028 """ 2029 ).indented() 2030 2031 val publishedApiSource: TestFile = kotlin( 2032 """ 2033 /** 2034 * When applied to a class or a member with internal visibility allows to use it from public inline functions and 2035 * makes it effectively public. 2036 * 2037 * Public inline functions cannot use non-public API, since if they are inlined, those non-public API references 2038 * would violate access restrictions at a call site (https://kotlinlang.org/docs/reference/inline-functions.html#public-inline-restrictions). 2039 * 2040 * To overcome this restriction an `internal` declaration can be annotated with the `@PublishedApi` annotation: 2041 * - this allows to call that declaration from public inline functions; 2042 * - the declaration becomes effectively public, and this should be considered with respect to binary compatibility maintaining. 2043 */ 2044 @Target(AnnotationTarget.CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) 2045 @Retention(AnnotationRetention.BINARY) 2046 @MustBeDocumented 2047 @SinceKotlin("1.1") 2048 public annotation class PublishedApi 2049 """ 2050 ).indented() 2051