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