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