# Test Modes
Lint's unit testing machinery has special support for “test modes”,
where it repeats a unit test under different conditions and makes sure
the test continues to pass with the same test results -- the same
warnings in the same test files.
There are a number of built-in test modes:
* Test modes which makes small tweaks to the source files which
should be compatible, such as
* Inserting unnecessary parentheses
* Replacing imported symbols with fully qualified names
* Replacing imported symbols with Kotlin import aliases
* Replacing types with typealiases
* Reordering Kotlin named arguments
* Replacing simple functions with Kotlin expression bodies
* etc
* A test mode which changes that detectors are correctly handling
Kotlin literal expressions by running them both with and without
the upcoming UiInjectionHost mode which will change the
representation of strings in an upcoming Kotlin version
* A partial analysis test mode which runs the tests in “partial
analysis” mode; two phases, analysis and reporting, with
minSdkVersion set to 1 during analysis and set to the true test
value during reporting etc.
* Bytecode Only: Any test files that specify both source and bytecode
will only use the bytecode
* Source Only: Any test files that specify both source and bytecode
will only use the source code
These are built-in test modes which will be applied to all detector
tests, but you can opt out of any test modes by invoking the
`skipTestModes` DSL method, as described below.
You can also add in your own test modes. For example, lint adds its own
internal test mode for making sure the built-in annotation checks works
with Android platform annotations in the following test mode:
[](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidPlatformAnnotationsTestMode.kt)
## How to debug
Let's say you have a test failure in a particular test mode, such
as `TestMode.PARENTHESIZED` which inserts unnecessary parentheses
into the source code to make sure detectors are properly skipping
through `UParenthesizedExpression` nodes.
If you just run this under the debugger, lint will run through
all the test modes as usual, which means you'll need to skip
through a lot of intermediate breakpoint hits.
For these scenarios, it's helpful to limit the test run to **only** the
target test mode. To do this, go and specify that specific test mode as
part of the run setup by adding the following method declaration into
your detector class:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
override fun lint(): TestLintTask {
return super.lint().testModes(TestMode.PARENTHESIZED)
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now when you run your test, it will run *only* this test mode, so you
can set breakpoints and start debugging through the scenario without
having to figure out which mode you're currently being invoked in.
!!! Warning
Don't forget to remove that override when you're done!
## Handling Intentional Failures
There are cases where your lint check is doing something very
particular related to the changes made by the test mode which means
that the test mode doesn't really apply. For example, there is a test
mode which adds unnecessary parentheses, to make sure that the detector
is properly handling the case where there are intermediate parenthesis
nodes in the AST. Normally, every lint check should behave the same
whether or not optional parentheses are present. But, if the lint check
you are writing is actually parenthesis specific, such as suggesting
removal of optional parentheses, then obviously in that case you don't
want to apply this test mode.
To do this, there's a special test DSL method you can add,
`skipTestModes`. Adding a comment for why that particular mode is
skipped is useful as well.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
lint().files(...)
.allowCompilationErrors()
// When running multiple passes of lint each pass will warn
// about the obsolete lint checks; that's fine
.skipTestModes(TestMode.PARTIAL)
.run()
.expectClean()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Source-Modifying Test Modes
The most powerful test modes are those that make some deliberate
transformations to your source code, to test variations of the code
patterns that may appear in the wild. Examples of this include having
optional parentheses, or fully qualified names.
Lint will make these transformations, then run your tests on the
modified sources, and make sure the results are the same -- except for
the part of the output which shows the relevant source code, since that
part is expected to differ due to the modifications.
When lint finds a failure, it will abort with a diff that includes not
just the different error output between the default mode and the source
modifying mode, but the source files as well; that makes it easier to
spot what the difference is.
In the following screenshot for example we've run a failing test inside
IntelliJ, and have then clicked on the Show Difference link in the test
output window (Ctrl+D or Cmd-D) which shows the test failure diff
nicely:
![Figure [fqn]: Screenshot of test failure](fully-qualified-error.png)
This is a test mode which converts all symbols to fully qualified
names; in addition to the labeled output at the top we can see the
diffs in the test case files below the error output diff. The test
files include line numbers to help make it easy to correlate extra or
missing warnings with their line numbers to the changed source code.
### Fully Qualified Names
The `TestMode.FULLY_QUALIFIED` test mode will rewrite the source files
such that all symbols that it can resolve are replaced with fully
qualified names.
For example, this mode will convert the following code:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
import android.widget.RemoteViews
fun test(packageName: String, other: Any) {
val rv = RemoteViews(packageName, R.layout.test)
val ov = other as RemoteViews
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
to
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
import android.widget.RemoteViews
fun test(packageName: String, other: Any) {
val rv = android.widget.RemoteViews(packageName, R.layout.test)
val ov = other as android.widget.RemoteViews
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This makes sure that your detector handles not only the case where a
symbol appears in its normal imported state, but also when it is fully
qualified in the code, perhaps because there is a different competing
class of the same name.
This will typically catch cases where the code is incorrectly just
comparing the identifier at the call node instead of the fully
qualified name.
For example, one detector's tests failed in this mode because
it was looking to identify references to an `EnumSet`,
and the code looked like this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
private fun checkEnumSet(node: UCallExpression) {
val receiver = node.receiver
if (receiver is USimpleNameReferenceExpression &&
receiver.identifier == "EnumSet"
) {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
which will work for code such as `EnumSet.of()` but not
`java.util.EnumSet.of()`.
Instead, use something like this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
private fun checkEnumSet(node: UCallExpression) {
val targetClass = node.resolve()?.containingClass?.qualifiedName
?: return
if (targetClass == "java.util.EnumSet") {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As with all the source transforming test modes, there are cases where
it doesn't apply. For example, lint had a built-in check for camera
EXIF metadata, encouraging you to import the androidx version of the
library instead of using the built-in version. If it sees you using the
platform one it will normally encourage you to import the androidx one
instead:
```
src/test/pkg/ExifUsage.java:9: Warning: Avoid using android.media.ExifInterface; use androidx.exifinterface.media.ExifInterface instead [ExifInterface]
android.media.ExifInterface exif = new android.media.ExifInterface(path);
---------------------------
```
However, if you explicitly (via fully qualified imports) reference the
platform one, in that case the lint check does not issue any warnings
since it figures you're deliberately trying to use the older version.
And in this test mode, the results between the two obviously differ,
and that's fine; as usual we'll deliberately turn off the check in this
detector:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~java
@Override protected TestLintTask lint() {
// This lint check deliberately treats fully qualified imports
// differently (they are interpreted as a deliberate usage of
// the discouraged API) so the fully qualified equivalence test
// does not apply:
return super.lint().skipTestModes(TestMode.FULLY_QUALIFIED);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! Tip
In Kotlin code, you may have code that checks to see if a node is a
`UCallExpression`. But note that if a call is fully qualified, the
node will be a `UQualifiedReferenceExpression` instead, and you'll
need to look at its selector. So watch out for code which does
something like `node as? UCallExpression`.
### Import Aliasing
In Kotlin, you can create an import alias, which lets you refer to
the imported class using an entirely different name.
This test mode will create import aliases for all the import statements
in the file and will replace all the references to the import aliases
instead. This makes sure that the detector handles the equivalent Kotlin
code.
For example, this mode will convert the following code:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
import android.widget.RemoteViews
fun test(packageName: String, other: Any) {
val rv = RemoteViews(packageName, R.layout.test)
val ov = other as RemoteViews
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
to
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~kotlin
import android.widget.RemoteViews as IMPORT_ALIAS_1_REMOTEVIEWS
fun test(packageName: String, other: Any) {
val rv = IMPORT_ALIAS_1_REMOTEVIEWS(packageName, R.layout.test)
val ov = other as IMPORT_ALIAS_1_REMOTEVIEWS
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Type Aliasing
Kotlin also lets you alias types using the `typealias` keyword.
This test mode is similar to import aliasing, but applied to all
types. In addition to the different AST representations of import
aliases and type aliases, they apply to different things.
For example, if we import TreeMap, and we have a code reference such as
`TreeMap