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