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