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