blob: 6b53b5c22a9d9274993907d154db75869d4caafc [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.room.compiler.processing.util
import androidx.room.compiler.processing.ExperimentalProcessingApi
import androidx.room.compiler.processing.XProcessingStep
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.runner.CompilationTestRunner
import androidx.room.compiler.processing.util.runner.JavacCompilationTestRunner
import androidx.room.compiler.processing.util.runner.KaptCompilationTestRunner
import androidx.room.compiler.processing.util.runner.KspCompilationTestRunner
import androidx.room.compiler.processing.util.runner.TestCompilationParameters
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.kspArgs
import com.tschuchort.compiletesting.symbolProcessorProviders
import java.io.ByteArrayOutputStream
import java.io.File
import javax.annotation.processing.Processor
@ExperimentalProcessingApi
private fun runTests(
params: TestCompilationParameters,
vararg runners: CompilationTestRunner
) {
val runCount = runners.count { runner ->
if (runner.canRun(params)) {
val compilationResult = runner.compile(params)
val subject = CompilationResultSubject.assertThat(compilationResult)
// if any assertion failed, throw first those.
subject.assertNoProcessorAssertionErrors()
compilationResult.processor.invocationInstances.forEach {
it.runPostCompilationChecks(subject)
}
assertWithMessage(
"compilation should've run the processor callback at least once"
).that(
compilationResult.processor.invocationInstances
).isNotEmpty()
subject.assertCompilationResult()
subject.assertAllExpectedRoundsAreCompleted()
true
} else {
false
}
}
// make sure some tests did run. Ksp tests might be disabled so if it is the only test given,
// ignore the check
val minTestCount = when {
CompilationTestCapabilities.canTestWithKsp ||
(runners.toList() - KspCompilationTestRunner).isNotEmpty() -> {
1
}
else -> {
// is ok if we don't run any tests if ksp is disabled and it is the only test
0
}
}
assertThat(runCount).isAtLeast(minTestCount)
}
@ExperimentalProcessingApi
fun runProcessorTestWithoutKsp(
sources: List<Source> = emptyList(),
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
handler: (XTestInvocation) -> Unit
) {
runTests(
params = TestCompilationParameters(
sources = sources,
classpath = classpath,
options = options,
handlers = listOf(handler)
),
JavacCompilationTestRunner,
KaptCompilationTestRunner
)
}
/**
* Runs the compilation test with ksp and one of javac or kapt, depending on whether input has
* kotlin sources.
*
* The [handler] will be invoked only for the first round. If you need to test multi round
* processing, use `handlers = listOf(..., ...)`.
*
* To assert on the compilation results, [handler] can call
* [XTestInvocation.assertCompilationResult] where it will receive a subject for post compilation
* assertions.
*
* By default, the compilation is expected to succeed. If it should fail, there must be an
* assertion on [XTestInvocation.assertCompilationResult] which expects a failure (e.g. checking
* errors).
*/
@ExperimentalProcessingApi
fun runProcessorTest(
sources: List<Source> = emptyList(),
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
handler: (XTestInvocation) -> Unit
) = runProcessorTest(
sources = sources,
classpath = classpath,
options = options,
handlers = listOf(handler)
)
/**
* Runs the step created by [createProcessingStep] with ksp and one of javac or kapt, depending
* on whether input has kotlin sources.
*
* The step created by [createProcessingStep] will be invoked only for the first round.
*
* [onCompilationResult] will be called with a [CompilationResultSubject] after each compilation to
* assert the compilation result.
*
* By default, the compilation is expected to succeed. If it should fail, there must be an
* assertion on [onCompilationResult] which expects a failure (e.g. checking errors).
*/
@Suppress("VisibleForTests") // this is a test library
@ExperimentalProcessingApi
fun runProcessorTest(
sources: List<Source> = emptyList(),
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
createProcessingStep: () -> XProcessingStep,
onCompilationResult: (CompilationResultSubject) -> Unit
) {
runProcessorTest(
sources = sources,
classpath = classpath,
options = options
) { invocation ->
val step = createProcessingStep()
val elements =
step.annotations()
.associateWith { annotation ->
invocation.roundEnv.getElementsAnnotatedWith(annotation)
.filterIsInstance<XTypeElement>()
.toSet()
}
step.process(
env = invocation.processingEnv,
elementsByAnnotation = elements
)
invocation.assertCompilationResult(onCompilationResult)
}
}
/**
* @see runProcessorTest
*/
@ExperimentalProcessingApi
fun runProcessorTest(
sources: List<Source> = emptyList(),
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
handlers: List<(XTestInvocation) -> Unit>
) {
val javaApRunner = if (sources.any { it is Source.KotlinSource }) {
KaptCompilationTestRunner
} else {
JavacCompilationTestRunner
}
runTests(
params = TestCompilationParameters(
sources = sources,
classpath = classpath.distinct(),
options = options,
handlers = handlers
),
javaApRunner,
KspCompilationTestRunner
)
}
/**
* Runs the test only with javac compilation backend.
*
* @see runProcessorTest
*/
@ExperimentalProcessingApi
fun runJavaProcessorTest(
sources: List<Source>,
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
handler: (XTestInvocation) -> Unit
) = runJavaProcessorTest(
sources = sources,
classpath = classpath,
options = options,
handlers = listOf(handler)
)
/**
* @see runJavaProcessorTest
*/
@ExperimentalProcessingApi
fun runJavaProcessorTest(
sources: List<Source>,
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
handlers: List<(XTestInvocation) -> Unit>
) {
runTests(
params = TestCompilationParameters(
sources = sources,
classpath = classpath,
options = options,
handlers = handlers
),
JavacCompilationTestRunner
)
}
/**
* Runs the test only with kapt compilation backend
*/
@ExperimentalProcessingApi
fun runKaptTest(
sources: List<Source>,
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
handler: (XTestInvocation) -> Unit
) = runKaptTest(
sources = sources,
classpath = classpath,
options = options,
handlers = listOf(handler)
)
/**
* @see runKaptTest
*/
@ExperimentalProcessingApi
fun runKaptTest(
sources: List<Source>,
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
handlers: List<(XTestInvocation) -> Unit>
) {
runTests(
params = TestCompilationParameters(
sources = sources,
classpath = classpath,
options = options,
handlers = handlers
),
KaptCompilationTestRunner
)
}
/**
* Runs the test only with ksp compilation backend
*/
@ExperimentalProcessingApi
fun runKspTest(
sources: List<Source>,
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
handler: (XTestInvocation) -> Unit
) = runKspTest(
sources = sources,
classpath = classpath,
options = options,
handlers = listOf(handler)
)
/**
* @see runKspTest
*/
@ExperimentalProcessingApi
fun runKspTest(
sources: List<Source>,
classpath: List<File> = emptyList(),
options: Map<String, String> = emptyMap(),
handlers: List<(XTestInvocation) -> Unit>
) {
runTests(
params = TestCompilationParameters(
sources = sources,
classpath = classpath,
options = options,
handlers = handlers
),
KspCompilationTestRunner
)
}
/**
* Compiles the given set of sources into a temporary folder and returns the full classpath that
* includes both the compilation output and dependencies.
*
* @param sources The list of source files to compile
* @param options The annotation processor arguments
* @param annotationProcessors The list of Java annotation processors to run with compilation
* @param symbolProcessorProviders The list of Kotlin symbol processor providers to run with
* compilation
* @param javacArguments The command line arguments that will be passed into javac
*/
fun compileFiles(
sources: List<Source>,
options: Map<String, String> = emptyMap(),
annotationProcessors: List<Processor> = emptyList(),
symbolProcessorProviders: List<SymbolProcessorProvider> = emptyList(),
javacArguments: List<String> = emptyList()
): List<File> {
val outputStream = ByteArrayOutputStream()
val compilation = KotlinCompilationUtil.prepareCompilation(
sources = sources,
outputStream = outputStream
)
if (annotationProcessors.isNotEmpty()) {
compilation.kaptArgs.putAll(options)
}
if (symbolProcessorProviders.isNotEmpty()) {
compilation.kspArgs.putAll(options)
}
compilation.javacArguments.addAll(javacArguments)
compilation.annotationProcessors = annotationProcessors
compilation.symbolProcessorProviders = symbolProcessorProviders
val result = compilation.compile()
check(result.exitCode == KotlinCompilation.ExitCode.OK) {
"compilation failed: ${outputStream.toString(Charsets.UTF_8)}"
}
return listOf(compilation.classesDir) + compilation.classpaths
}