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