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