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