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