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