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