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