New processing test APIs

This CL updates the compilation testing infra and refactors the
internals.
The biggest change is that now there is an API to assert compilation
results rather than calling `runXXXforFailed` methods.
In this new API, the handler is responsible to make a failure assertion
if the compilation should fail, otherwise, test will expect compilation
to succeed and fail the test if the compilation fails.

As we add tests in Room, I'll add APIs to also assert generated code but
right now we don't have that use case hence not adding them (also, it is
not clear yet how it will work with kotlin compile testing).

To be able to collect diagnostics before it goes to the compilation
infra, I've moved XMessager to an abstract class (from interface) and
add ability to watch messages. Unfortunately, kotlin compile testing
does not provide an API to get messages with diagnostic types which is
why we have the abstraction on our end.

Bug: 160322705
Test: existing tests
Change-Id: I0463b1bdc822e3e1e7fd39f3e5e17581c3bfde19
diff --git a/room/compiler-processing-testing/build.gradle b/room/compiler-processing-testing/build.gradle
index 0edce59..3fa24a0 100644
--- a/room/compiler-processing-testing/build.gradle
+++ b/room/compiler-processing-testing/build.gradle
@@ -26,7 +26,7 @@
 
 dependencies {
     implementation("androidx.annotation:annotation:1.1.0")
-    implementation(project(":room:room-compiler-processing"))
+    api(project(":room:room-compiler-processing"))
     implementation(KOTLIN_STDLIB)
     implementation(KOTLIN_KSP_API)
     testImplementation(KOTLIN_KSP)
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticJavacProcessor.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticJavacProcessor.kt
index 0ac5928..54fc124 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticJavacProcessor.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticJavacProcessor.kt
@@ -16,21 +16,27 @@
 
 package androidx.room.compiler.processing
 
+import androidx.room.compiler.processing.util.RecordingXMessager
 import androidx.room.compiler.processing.util.XTestInvocation
-import java.lang.AssertionError
 import javax.lang.model.SourceVersion
 
 class SyntheticJavacProcessor(
-    val handler: (XTestInvocation) -> Unit
-) : JavacTestProcessor() {
+    val handler: (XTestInvocation) -> Unit,
+) : JavacTestProcessor(), SyntheticProcessor {
+    override val invocationInstances = mutableListOf<XTestInvocation>()
     private var result: Result<Unit>? = null
+    override val messageWatcher = RecordingXMessager()
 
     override fun doProcess(annotations: Set<XTypeElement>, roundEnv: XRoundEnv): Boolean {
+        val xEnv = XProcessingEnv.create(processingEnv)
+        xEnv.messager.addMessageWatcher(messageWatcher)
         result = kotlin.runCatching {
             handler(
                 XTestInvocation(
-                    processingEnv = XProcessingEnv.create(processingEnv)
-                )
+                    processingEnv = xEnv
+                ).also {
+                    invocationInstances.add(it)
+                }
             )
         }
         return true
@@ -42,7 +48,7 @@
 
     override fun getSupportedAnnotationTypes() = setOf("*")
 
-    fun throwIfFailed() {
+    override fun throwIfFailed() {
         val result = checkNotNull(result) {
             "did not compile"
         }
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticKspProcessor.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticKspProcessor.kt
index 106d6b1..68ec813 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticKspProcessor.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticKspProcessor.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.compiler.processing
 
+import androidx.room.compiler.processing.util.RecordingXMessager
 import androidx.room.compiler.processing.util.XTestInvocation
 import com.google.devtools.ksp.processing.CodeGenerator
 import com.google.devtools.ksp.processing.KSPLogger
@@ -24,11 +25,14 @@
 
 class SyntheticKspProcessor(
     private val handler: (XTestInvocation) -> Unit
-) : SymbolProcessor {
+) : SymbolProcessor, SyntheticProcessor {
+    override val invocationInstances = mutableListOf<XTestInvocation>()
     private var result: Result<Unit>? = null
     private lateinit var options: Map<String, String>
     private lateinit var codeGenerator: CodeGenerator
     private lateinit var logger: KSPLogger
+    override val messageWatcher = RecordingXMessager()
+
     override fun finish() {
     }
 
@@ -44,21 +48,25 @@
     }
 
     override fun process(resolver: Resolver) {
+        val xEnv = XProcessingEnv.create(
+            options,
+            resolver,
+            codeGenerator,
+            logger
+        )
+        xEnv.messager.addMessageWatcher(messageWatcher)
         result = kotlin.runCatching {
             handler(
                 XTestInvocation(
-                    processingEnv = XProcessingEnv.create(
-                        options,
-                        resolver,
-                        codeGenerator,
-                        logger
-                    )
-                )
+                    processingEnv = xEnv
+                ).also {
+                    invocationInstances.add(it)
+                }
             )
         }
     }
 
-    fun throwIfFailed() {
+    override fun throwIfFailed() {
         val result = checkNotNull(result) {
             "did not compile"
         }
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticProcessor.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticProcessor.kt
new file mode 100644
index 0000000..05cc826
--- /dev/null
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticProcessor.kt
@@ -0,0 +1,45 @@
+/*
+ * 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
+
+import androidx.room.compiler.processing.util.RecordingXMessager
+import androidx.room.compiler.processing.util.XTestInvocation
+
+/**
+ * Common interface for SyntheticProcessors that we create for testing.
+ */
+internal interface SyntheticProcessor {
+    /**
+     * List of invocations that was sent to the test code.
+     *
+     * The test code can register assertions on the compilation result, which is why we need this
+     * list (to run assertions after compilation).
+     */
+    val invocationInstances: List<XTestInvocation>
+
+    /**
+     * The recorder for messages where we'll grab the diagnostics.
+     */
+    val messageWatcher: RecordingXMessager
+
+    /**
+     * Should throw if processor did throw an exception.
+     * When assertions fail, we don't fail the compilation to keep the stack trace, instead,
+     * dispatch them afterwards.
+     */
+    fun throwIfFailed()
+}
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
new file mode 100644
index 0000000..3dbbff7
--- /dev/null
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.SyntheticJavacProcessor
+import androidx.room.compiler.processing.SyntheticProcessor
+import androidx.room.compiler.processing.util.runner.CompilationTestRunner
+import com.google.common.truth.Fact.simpleFact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+import com.google.testing.compile.Compilation
+import com.google.testing.compile.CompileTester
+import com.tschuchort.compiletesting.KotlinCompilation
+import javax.tools.Diagnostic
+
+/**
+ * Holds the information about a test compilation result.
+ */
+abstract class CompilationResult internal constructor(
+    /**
+     * The test infra which run this test
+     */
+    internal val testRunnerName: String,
+    /**
+     * The [SyntheticProcessor] used in this compilation.
+     */
+    internal val processor: SyntheticProcessor,
+    /**
+     * True if compilation result was success.
+     */
+    internal val successfulCompilation: Boolean,
+) {
+    private val diagnostics = processor.messageWatcher.diagnostics()
+
+    fun diagnosticsOfKind(kind: Diagnostic.Kind) = diagnostics[kind].orEmpty()
+
+    override fun toString(): String {
+        return buildString {
+            appendLine("CompilationResult (with $testRunnerName)")
+            Diagnostic.Kind.values().forEach { kind ->
+                val messages = diagnosticsOfKind(kind)
+                appendLine("${kind.name}: ${messages.size}")
+                messages.forEach {
+                    appendLine(it)
+                }
+                appendLine()
+            }
+        }
+    }
+}
+
+/**
+ * Truth subject that can run assertions on the [CompilationResult].
+ * see: [XTestInvocation.assertCompilationResult]
+ */
+class CompilationResultSubject(
+    failureMetadata: FailureMetadata,
+    val compilationResult: CompilationResult,
+) : Subject<CompilationResultSubject, CompilationResult>(
+    failureMetadata, compilationResult
+) {
+    /**
+     * set to true if any assertion on the subject requires it to fail (e.g. looking for errors)
+     */
+    internal var shouldSucceed: Boolean = true
+
+    /**
+     * Asserts that compilation did fail. This covers the cases where the processor won't print
+     * any diagnostics but compilation will still fail (e.g. bad generated code).
+     *
+     * @see hasError
+     */
+    fun compilationDidFail() = chain {
+        shouldSucceed = false
+    }
+
+    /**
+     * Asserts that compilation has a warning with the given text.
+     *
+     * @see hasError
+     */
+    fun hasWarning(expected: String) = chain {
+        hasDiagnosticWithMessage(
+            kind = Diagnostic.Kind.WARNING,
+            expected = expected
+        ) {
+            "expected warning: $expected"
+        }
+    }
+
+    /**
+     * Asserts that compilation has an error with the given text.
+     *
+     * @see hasWarning
+     */
+    fun hasError(expected: String) = chain {
+        shouldSucceed = false
+        hasDiagnosticWithMessage(
+            kind = Diagnostic.Kind.ERROR,
+            expected = expected
+        ) {
+            "expected error: $expected"
+        }
+    }
+
+    /**
+     * Asserts that compilation has at least one diagnostics message with kind error.
+     *
+     * @see compilationDidFail
+     * @see hasWarning
+     */
+    fun hasError() = chain {
+        shouldSucceed = false
+        if (actual().diagnosticsOfKind(Diagnostic.Kind.ERROR).isEmpty()) {
+            failWithActual(
+                simpleFact("expected at least one failure message")
+            )
+        }
+    }
+
+    /**
+     * Called after handler is invoked to check its compilation failure assertion against the
+     * compilation result.
+     */
+    internal fun assertCompilationResult() {
+        if (compilationResult.successfulCompilation != shouldSucceed) {
+            failWithActual(
+                simpleFact(
+                    "expected compilation result to be: $shouldSucceed but was " +
+                        "${compilationResult.successfulCompilation}"
+                )
+            )
+        }
+    }
+
+    private fun hasDiagnosticWithMessage(
+        kind: Diagnostic.Kind,
+        expected: String,
+        buildErrorMessage: () -> String
+    ) {
+        val diagnostics = compilationResult.diagnosticsOfKind(kind)
+        if (diagnostics.any { it.msg == expected }) {
+            return
+        }
+        failWithActual(simpleFact(buildErrorMessage()))
+    }
+
+    private fun chain(
+        block: () -> Unit
+    ): CompileTester.ChainingClause<CompilationResultSubject> {
+        block()
+        return CompileTester.ChainingClause<CompilationResultSubject> {
+            this
+        }
+    }
+
+    companion object {
+        private val FACTORY =
+            Factory<CompilationResultSubject, CompilationResult> { metadata, actual ->
+                CompilationResultSubject(metadata, actual)
+            }
+
+        fun assertThat(
+            compilationResult: CompilationResult
+        ): CompilationResultSubject {
+            return Truth.assertAbout(FACTORY).that(
+                compilationResult
+            )
+        }
+    }
+}
+
+internal class JavaCompileTestingCompilationResult(
+    testRunner: CompilationTestRunner,
+    @Suppress("unused")
+    private val delegate: Compilation,
+    processor: SyntheticJavacProcessor
+) : CompilationResult(
+    testRunnerName = testRunner.name,
+    processor = processor,
+    successfulCompilation = delegate.status() == Compilation.Status.SUCCESS
+)
+
+internal class KotlinCompileTestingCompilationResult(
+    testRunner: CompilationTestRunner,
+    @Suppress("unused")
+    private val delegate: KotlinCompilation.Result,
+    processor: SyntheticProcessor,
+    successfulCompilation: Boolean
+) : CompilationResult(
+    testRunnerName = testRunner.name,
+    processor = processor,
+    successfulCompilation = successfulCompilation
+)
\ No newline at end of file
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/DiagnosticMessage.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/DiagnosticMessage.kt
new file mode 100644
index 0000000..34ef8e3
--- /dev/null
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/DiagnosticMessage.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.XElement
+
+/**
+ * Holder for diagnostics messages
+ */
+data class DiagnosticMessage(
+    val msg: String,
+    val element: XElement?
+)
\ No newline at end of file
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
index 076b531..6eeb2c1 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
@@ -16,125 +16,43 @@
 
 package androidx.room.compiler.processing.util
 
-import androidx.room.compiler.processing.SyntheticJavacProcessor
-import androidx.room.compiler.processing.SyntheticKspProcessor
-import com.google.common.truth.Truth
+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.testing.compile.CompileTester
-import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.google.common.truth.Truth.assertWithMessage
 import com.tschuchort.compiletesting.KotlinCompilation
-import com.tschuchort.compiletesting.SourceFile
-import com.tschuchort.compiletesting.kspSourcesDir
-import com.tschuchort.compiletesting.symbolProcessors
 import java.io.File
 
-// TODO get rid of these once kotlin compile testing supports two step compilation for KSP.
-//  https://github.com/tschuchortdev/kotlin-compile-testing/issues/72
-private val KotlinCompilation.kspJavaSourceDir: File
-    get() = kspSourcesDir.resolve("java")
+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)
+            compilationResult.processor.invocationInstances.forEach {
+                it.runPostCompilationChecks(subject)
+            }
+            assertWithMessage(
+                "compilation should've run the processor callback at least once"
+            ).that(
+                compilationResult.processor.invocationInstances
+            ).isNotEmpty()
 
-private val KotlinCompilation.kspKotlinSourceDir: File
-    get() = kspSourcesDir.resolve("kotlin")
+            subject.assertCompilationResult()
 
-private fun compileSources(
-    sources: List<Source>,
-    classpath: List<File>,
-    handler: (XTestInvocation) -> Unit
-): Pair<SyntheticJavacProcessor, CompileTester> {
-    val syntheticJavacProcessor = SyntheticJavacProcessor(handler)
-    return syntheticJavacProcessor to Truth.assertAbout(
-        JavaSourcesSubjectFactory.javaSources()
-    ).that(
-        sources.map {
-            it.toJFO()
+            compilationResult.processor.throwIfFailed()
+            true
+        } else {
+            false
         }
-    ).apply {
-        if (classpath.isNotEmpty()) {
-            withClasspath(classpath)
-        }
-    }.processedWith(
-        syntheticJavacProcessor
-    )
-}
-
-private fun compileWithKapt(
-    sources: List<Source>,
-    classpath: List<File>,
-    handler: (XTestInvocation) -> Unit
-): Pair<SyntheticJavacProcessor, KotlinCompilation> {
-    val syntheticJavacProcessor = SyntheticJavacProcessor(handler)
-    val compilation = KotlinCompilation()
-    sources.forEach {
-        compilation.workingDir.resolve("sources")
-            .resolve(it.relativePath())
-            .parentFile
-            .mkdirs()
     }
-    compilation.sources = sources.map {
-        it.toKotlinSourceFile()
-    }
-    compilation.annotationProcessors = listOf(syntheticJavacProcessor)
-    compilation.inheritClassPath = true
-    compilation.verbose = false
-    compilation.classpaths += classpath
-
-    return syntheticJavacProcessor to compilation
-}
-
-private fun compileWithKsp(
-    sources: List<Source>,
-    classpath: List<File>,
-    handler: (XTestInvocation) -> Unit
-): Pair<SyntheticKspProcessor, KotlinCompilation.Result> {
-    @Suppress("NAME_SHADOWING")
-    val sources = if (sources.none { it is Source.KotlinSource }) {
-        // looks like this requires a kotlin source file
-        // see: https://github.com/tschuchortdev/kotlin-compile-testing/issues/57
-        sources + Source.kotlin("placeholder.kt", "")
-    } else {
-        sources
-    }
-    val syntheticKspProcessor = SyntheticKspProcessor(handler)
-    fun prepareCompilation(): KotlinCompilation {
-        val compilation = KotlinCompilation()
-        sources.forEach {
-            compilation.workingDir.resolve("sources")
-                .resolve(it.relativePath())
-                .parentFile
-                .mkdirs()
-        }
-        compilation.sources = sources.map {
-            it.toKotlinSourceFile()
-        }
-        compilation.jvmDefault = "enable"
-        compilation.jvmTarget = "1.8"
-        compilation.inheritClassPath = true
-        compilation.verbose = false
-        compilation.classpaths += classpath
-        return compilation
-    }
-
-    val kspCompilation = prepareCompilation()
-    kspCompilation.symbolProcessors = listOf(syntheticKspProcessor)
-    kspCompilation.compile()
-    // ignore KSP result for now because KSP stops compilation, which might create false negatives
-    // when java code accesses kotlin code.
-    // TODO:  fix once https://github.com/tschuchortdev/kotlin-compile-testing/issues/72 is fixed
-
-    // after ksp, compile without ksp with KSP's output as input
-    val finalCompilation = prepareCompilation()
-    // build source files from generated code
-    finalCompilation.sources += kspCompilation.kspJavaSourceDir.collectSourceFiles() +
-        kspCompilation.kspKotlinSourceDir.collectSourceFiles()
-    return syntheticKspProcessor to finalCompilation.compile()
-}
-
-private fun File.collectSourceFiles(): List<SourceFile> {
-    return walkTopDown().filter {
-        it.isFile
-    }.map { file ->
-        SourceFile.fromPath(file)
-    }.toList()
+    // make sure some tests did run
+    assertThat(runCount).isGreaterThan(0)
 }
 
 fun runProcessorTest(
@@ -142,149 +60,102 @@
     classpath: List<File> = emptyList(),
     handler: (XTestInvocation) -> Unit
 ) {
-    @Suppress("NAME_SHADOWING")
-    val sources = if (sources.isEmpty()) {
-        // synthesize a source to trigger compilation
-        listOf(
-            Source.java(
-                "foo.bar.SyntheticSource",
-                """
-            package foo.bar;
-            public class SyntheticSource {}
-                """.trimIndent()
-            )
-        )
-    } else {
-        sources
-    }
-    // we can compile w/ javac only if all code is in java
-    if (sources.canCompileWithJava()) {
-        runJavaProcessorTest(
+    runTests(
+        params = TestCompilationParameters(
             sources = sources,
             classpath = classpath,
-            handler = handler,
-            succeed = true
-        )
-    }
-    runKaptTest(
-        sources = sources,
-        classpath = classpath,
-        handler = handler,
-        succeed = true
+            handler = handler
+        ),
+        JavacCompilationTestRunner,
+        KaptCompilationTestRunner
     )
 }
 
 /**
- * This method is oddly named instead of being an overload on runProcessorTest to easily track
- * which tests started to support KSP.
+ * Runs the compilation test with all 3 backends (javac, kapt, ksp) if possible (e.g. javac
+ * cannot test kotlin sources).
  *
- * Eventually, it will be merged with runProcessorTest when all tests pass with KSP.
+ * The [handler] will be invoked for each compilation hence it should be repeatable.
+ *
+ * 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).
  */
 fun runProcessorTestIncludingKsp(
     sources: List<Source> = emptyList(),
     classpath: List<File> = emptyList(),
     handler: (XTestInvocation) -> Unit
 ) {
-    runProcessorTest(
-        sources = sources,
-        classpath = classpath,
-        handler = handler
-    )
-    runKspTest(
-        sources = sources,
-        classpath = classpath,
-        succeed = true,
-        handler = handler
-    )
-}
-
-fun runProcessorTestForFailedCompilation(
-    sources: List<Source>,
-    classpath: List<File> = emptyList(),
-    handler: (XTestInvocation) -> Unit
-) {
-    if (sources.canCompileWithJava()) {
-        // run with java processor
-        runJavaProcessorTest(
+    runTests(
+        params = TestCompilationParameters(
             sources = sources,
             classpath = classpath,
-            handler = handler,
-            succeed = false
-        )
-    }
-    // now run with kapt
-    runKaptTest(
-        sources = sources,
-        classpath = classpath,
-        handler = handler,
-        succeed = false
+            handler = handler
+        ),
+        JavacCompilationTestRunner,
+        KaptCompilationTestRunner,
+        KspCompilationTestRunner
     )
 }
 
-fun runProcessorTestForFailedCompilationIncludingKsp(
-    sources: List<Source>,
-    classpath: List<File>,
-    handler: (XTestInvocation) -> Unit
-) {
-    runProcessorTestForFailedCompilation(
-        sources = sources,
-        classpath = classpath,
-        handler = handler
-    )
-    // now run with ksp
-    runKspTest(
-        sources = sources,
-        classpath = classpath,
-        handler = handler,
-        succeed = false
-    )
-}
-
+/**
+ * Runs the test only with javac compilation backend.
+ *
+ * @see runProcessorTestIncludingKsp
+ */
 fun runJavaProcessorTest(
     sources: List<Source>,
     classpath: List<File>,
-    succeed: Boolean,
     handler: (XTestInvocation) -> Unit
 ) {
-    val (syntheticJavacProcessor, compileTester) = compileSources(sources, classpath, handler)
-    if (succeed) {
-        compileTester.compilesWithoutError()
-    } else {
-        compileTester.failsToCompile()
-    }
-    syntheticJavacProcessor.throwIfFailed()
+    runTests(
+        params = TestCompilationParameters(
+            sources = sources,
+            classpath = classpath,
+            handler = handler
+        ),
+        JavacCompilationTestRunner
+    )
 }
 
+/**
+ * Runs the test only with kapt compilation backend
+ */
 fun runKaptTest(
     sources: List<Source>,
     classpath: List<File> = emptyList(),
-    succeed: Boolean = true,
     handler: (XTestInvocation) -> Unit
 ) {
-    // now run with kapt
-    val (kaptProcessor, kotlinCompilation) = compileWithKapt(sources, classpath, handler)
-    val compilationResult = kotlinCompilation.compile()
-    if (succeed) {
-        assertThat(compilationResult.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
-    } else {
-        assertThat(compilationResult.exitCode).isNotEqualTo(KotlinCompilation.ExitCode.OK)
-    }
-    kaptProcessor.throwIfFailed()
+    runTests(
+        params = TestCompilationParameters(
+            sources = sources,
+            classpath = classpath,
+            handler = handler
+        ),
+        KaptCompilationTestRunner
+    )
 }
 
+/**
+ * Runs the test only with ksp compilation backend
+ */
 fun runKspTest(
     sources: List<Source>,
     classpath: List<File> = emptyList(),
-    succeed: Boolean = true,
     handler: (XTestInvocation) -> Unit
 ) {
-    val (kspProcessor, compilationResult) = compileWithKsp(sources, classpath, handler)
-    if (succeed) {
-        assertThat(compilationResult.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
-    } else {
-        assertThat(compilationResult.exitCode).isNotEqualTo(KotlinCompilation.ExitCode.OK)
-    }
-    kspProcessor.throwIfFailed()
+    runTests(
+        params = TestCompilationParameters(
+            sources = sources,
+            classpath = classpath,
+            handler = handler
+        ),
+        KspCompilationTestRunner
+    )
 }
 
 /**
@@ -314,5 +185,3 @@
     }
     return compilation.classesDir
 }
-
-private fun List<Source>.canCompileWithJava() = all { it is Source.JavaSource }
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/RecordingXMessager.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/RecordingXMessager.kt
new file mode 100644
index 0000000..c2aa5dc
--- /dev/null
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/RecordingXMessager.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.XElement
+import androidx.room.compiler.processing.XMessager
+import javax.tools.Diagnostic
+
+/**
+ * An XMessager implementation that holds onto dispatched diagnostics.
+ */
+class RecordingXMessager : XMessager() {
+    private val diagnostics = mutableMapOf<Diagnostic.Kind, MutableList<DiagnosticMessage>>()
+
+    fun diagnostics(): Map<Diagnostic.Kind, List<DiagnosticMessage>> = diagnostics
+
+    override fun onPrintMessage(kind: Diagnostic.Kind, msg: String, element: XElement?) {
+        diagnostics.getOrPut(
+            kind
+        ) {
+            mutableListOf()
+        }.add(
+            DiagnosticMessage(
+                msg = msg,
+                element = element
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt
index 267c140..add6bd4 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt
@@ -24,6 +24,25 @@
 class XTestInvocation(
     val processingEnv: XProcessingEnv,
 ) {
+    private val postCompilationAssertions = mutableListOf<CompilationResultSubject.() -> Unit>()
     val isKsp: Boolean
         get() = processingEnv.backend == XProcessingEnv.Backend.KSP
+
+    /**
+     * Registers a block that will be called with a [CompilationResultSubject] when compilation
+     * finishes.
+     *
+     * Note that it is not safe to access the environment in this block.
+     */
+    fun assertCompilationResult(block: CompilationResultSubject.() -> Unit) {
+        postCompilationAssertions.add(block)
+    }
+
+    internal fun runPostCompilationChecks(
+        compilationResultSubject: CompilationResultSubject
+    ) {
+        postCompilationAssertions.forEach {
+            it(compilationResultSubject)
+        }
+    }
 }
\ No newline at end of file
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/CompilationTestRunner.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/CompilationTestRunner.kt
new file mode 100644
index 0000000..257e58a
--- /dev/null
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/CompilationTestRunner.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.runner
+
+import androidx.room.compiler.processing.util.CompilationResult
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import java.io.File
+
+/**
+ * Common interface for compilation tests
+ */
+internal interface CompilationTestRunner {
+    // user visible name that we can print in assertions
+    val name: String
+
+    fun canRun(params: TestCompilationParameters): Boolean
+
+    fun compile(params: TestCompilationParameters): CompilationResult
+}
+
+internal data class TestCompilationParameters(
+    val sources: List<Source> = emptyList(),
+    val classpath: List<File> = emptyList(),
+    val handler: (XTestInvocation) -> Unit
+)
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/JavacCompilationTestRunner.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/JavacCompilationTestRunner.kt
new file mode 100644
index 0000000..5fdba61
--- /dev/null
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/JavacCompilationTestRunner.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.runner
+
+import androidx.room.compiler.processing.SyntheticJavacProcessor
+import androidx.room.compiler.processing.util.CompilationResult
+import androidx.room.compiler.processing.util.JavaCompileTestingCompilationResult
+import androidx.room.compiler.processing.util.Source
+import com.google.testing.compile.Compiler
+
+internal object JavacCompilationTestRunner : CompilationTestRunner {
+
+    override val name: String = "javac"
+
+    override fun canRun(params: TestCompilationParameters): Boolean {
+        return params.sources.all { it is Source.JavaSource }
+    }
+
+    override fun compile(params: TestCompilationParameters): CompilationResult {
+        val syntheticJavacProcessor = SyntheticJavacProcessor(params.handler)
+        val sources = if (params.sources.isEmpty()) {
+            // synthesize a source to trigger compilation
+            listOf(
+                Source.java(
+                    qName = "foo.bar.SyntheticSource",
+                    code = """
+                    package foo.bar;
+                    public class SyntheticSource {}
+                    """.trimIndent()
+                )
+            )
+        } else {
+            params.sources
+        }
+        val compiler = Compiler
+            .javac()
+            .withProcessors(syntheticJavacProcessor)
+            .withOptions("-Xlint")
+            .let {
+                if (params.classpath.isNotEmpty()) {
+                    it.withClasspath(params.classpath)
+                } else {
+                    it
+                }
+            }
+        val javaFileObjects = sources.map {
+            it.toJFO()
+        }
+        val compilation = compiler.compile(javaFileObjects)
+        return JavaCompileTestingCompilationResult(
+            testRunner = this,
+            delegate = compilation,
+            processor = syntheticJavacProcessor
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/KaptCompilationTestRunner.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/KaptCompilationTestRunner.kt
new file mode 100644
index 0000000..e6cb5ee
--- /dev/null
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/KaptCompilationTestRunner.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.runner
+
+import androidx.room.compiler.processing.SyntheticJavacProcessor
+import androidx.room.compiler.processing.util.CompilationResult
+import androidx.room.compiler.processing.util.KotlinCompileTestingCompilationResult
+import com.tschuchort.compiletesting.KotlinCompilation
+
+internal object KaptCompilationTestRunner : CompilationTestRunner {
+
+    override val name: String = "kapt"
+
+    override fun canRun(params: TestCompilationParameters): Boolean {
+        return true
+    }
+
+    override fun compile(params: TestCompilationParameters): CompilationResult {
+        val syntheticJavacProcessor = SyntheticJavacProcessor(params.handler)
+        val compilation = KotlinCompilation()
+        params.sources.forEach {
+            compilation.workingDir.resolve("sources")
+                .resolve(it.relativePath())
+                .parentFile
+                .mkdirs()
+        }
+        compilation.sources = params.sources.map {
+            it.toKotlinSourceFile()
+        }
+        compilation.annotationProcessors = listOf(syntheticJavacProcessor)
+        compilation.inheritClassPath = true
+        compilation.verbose = false
+        compilation.classpaths += params.classpath
+
+        val result = compilation.compile()
+        return KotlinCompileTestingCompilationResult(
+            testRunner = this,
+            delegate = result,
+            processor = syntheticJavacProcessor,
+            successfulCompilation = result.exitCode == KotlinCompilation.ExitCode.OK
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/KspCompilationTestRunner.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/KspCompilationTestRunner.kt
new file mode 100644
index 0000000..8ac5ed3
--- /dev/null
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/runner/KspCompilationTestRunner.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.runner
+
+import androidx.room.compiler.processing.SyntheticKspProcessor
+import androidx.room.compiler.processing.util.CompilationResult
+import androidx.room.compiler.processing.util.KotlinCompileTestingCompilationResult
+import androidx.room.compiler.processing.util.Source
+import com.tschuchort.compiletesting.KotlinCompilation
+import com.tschuchort.compiletesting.SourceFile
+import com.tschuchort.compiletesting.kspSourcesDir
+import com.tschuchort.compiletesting.symbolProcessors
+import java.io.File
+import javax.tools.Diagnostic
+
+internal object KspCompilationTestRunner : CompilationTestRunner {
+
+    override val name: String = "ksp"
+
+    override fun canRun(params: TestCompilationParameters): Boolean {
+        return true
+    }
+
+    override fun compile(params: TestCompilationParameters): CompilationResult {
+        @Suppress("NAME_SHADOWING")
+        val sources = if (params.sources.none { it is Source.KotlinSource }) {
+            // looks like this requires a kotlin source file
+            // see: https://github.com/tschuchortdev/kotlin-compile-testing/issues/57
+            params.sources + Source.kotlin("placeholder.kt", "")
+        } else {
+            params.sources
+        }
+        val syntheticKspProcessor = SyntheticKspProcessor(params.handler)
+        fun prepareCompilation(): KotlinCompilation {
+            val compilation = KotlinCompilation()
+            sources.forEach {
+                compilation.workingDir.resolve("sources")
+                    .resolve(it.relativePath())
+                    .parentFile
+                    .mkdirs()
+            }
+            compilation.sources = sources.map {
+                it.toKotlinSourceFile()
+            }
+            compilation.jvmDefault = "enable"
+            compilation.jvmTarget = "1.8"
+            compilation.inheritClassPath = true
+            compilation.verbose = false
+            compilation.classpaths += params.classpath
+            return compilation
+        }
+
+        val kspCompilation = prepareCompilation()
+        kspCompilation.symbolProcessors = listOf(syntheticKspProcessor)
+        kspCompilation.compile()
+        // ignore KSP result for now because KSP stops compilation, which might create false
+        // negatives when java code accesses kotlin code.
+        // TODO:  fix once https://github.com/tschuchortdev/kotlin-compile-testing/issues/72 is
+        //  fixed
+
+        // after ksp, compile without ksp with KSP's output as input
+        val finalCompilation = prepareCompilation()
+        // build source files from generated code
+        finalCompilation.sources += kspCompilation.kspJavaSourceDir.collectSourceFiles() +
+            kspCompilation.kspKotlinSourceDir.collectSourceFiles()
+        val result = finalCompilation.compile()
+        // workaround for: https://github.com/google/ksp/issues/122
+        // KSP does not fail compilation for error diagnostics hence we do it here.
+        val hasErrorDiagnostics = syntheticKspProcessor.messageWatcher
+            .diagnostics()[Diagnostic.Kind.ERROR].orEmpty().isNotEmpty()
+        return KotlinCompileTestingCompilationResult(
+            testRunner = this,
+            delegate = result,
+            processor = syntheticKspProcessor,
+            successfulCompilation = result.exitCode == KotlinCompilation.ExitCode.OK &&
+                !hasErrorDiagnostics
+
+        )
+    }
+
+    // TODO get rid of these once kotlin compile testing supports two step compilation for KSP.
+    //  https://github.com/tschuchortdev/kotlin-compile-testing/issues/72
+    private val KotlinCompilation.kspJavaSourceDir: File
+        get() = kspSourcesDir.resolve("java")
+
+    private val KotlinCompilation.kspKotlinSourceDir: File
+        get() = kspSourcesDir.resolve("kotlin")
+
+    private fun File.collectSourceFiles(): List<SourceFile> {
+        return walkTopDown().filter {
+            it.isFile
+        }.map { file ->
+            SourceFile.fromPath(file)
+        }.toList()
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt b/room/compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
new file mode 100644
index 0000000..3d188b4a
--- /dev/null
+++ b/room/compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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 com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.TypeSpec
+import org.junit.Test
+import javax.tools.Diagnostic
+
+class TestRunnerTest {
+    @Test
+    fun generatedBadCode_expected() = generatedBadCode(assertFailure = true)
+
+    @Test(expected = AssertionError::class)
+    fun generatedBadCode_unexpected() = generatedBadCode(assertFailure = false)
+
+    private fun generatedBadCode(assertFailure: Boolean) {
+        runProcessorTestIncludingKsp {
+            if (it.processingEnv.findTypeElement("foo.Foo") == null) {
+                val badCode = TypeSpec.classBuilder("Foo").apply {
+                    addStaticBlock(
+                        CodeBlock.of("bad code")
+                    )
+                }.build()
+                val badGeneratedFile = JavaFile.builder("foo", badCode).build()
+                it.processingEnv.filer.write(
+                    badGeneratedFile
+                )
+            }
+            if (assertFailure) {
+                it.assertCompilationResult {
+                    compilationDidFail()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun reportedError_expected() = reportedError(assertFailure = true)
+
+    @Test(expected = AssertionError::class)
+    fun reportedError_unexpected() = reportedError(assertFailure = false)
+
+    fun reportedError(assertFailure: Boolean) {
+        runProcessorTestIncludingKsp {
+            it.processingEnv.messager.printMessage(
+                kind = Diagnostic.Kind.ERROR,
+                msg = "reported error"
+            )
+            if (assertFailure) {
+                it.assertCompilationResult {
+                    hasError("reported error")
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-processing/build.gradle b/room/compiler-processing/build.gradle
index 795a691..b15e171 100644
--- a/room/compiler-processing/build.gradle
+++ b/room/compiler-processing/build.gradle
@@ -26,12 +26,13 @@
 }
 
 dependencies {
+    api(KOTLIN_STDLIB)
+    api(JAVAPOET)
     implementation("androidx.annotation:annotation:1.1.0")
     implementation(GUAVA)
-    implementation(KOTLIN_STDLIB)
     implementation(AUTO_COMMON)
     implementation(AUTO_VALUE_ANNOTATIONS)
-    implementation(JAVAPOET)
+
     implementation(KOTLIN_METADATA_JVM)
     implementation(INTELLIJ_ANNOTATIONS)
     implementation(KOTLIN_KSP_API) {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XMessager.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XMessager.kt
index 0701c52..29ff818 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XMessager.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XMessager.kt
@@ -21,7 +21,8 @@
 /**
  * Logging interface for the processor
  */
-interface XMessager {
+abstract class XMessager {
+    private val watchers = mutableListOf<XMessager>()
     /**
      * Prints the given [msg] to the logs while also associating it with the given [element].
      *
@@ -29,5 +30,20 @@
      * @param msg The actual message to report to the compiler
      * @param element The element with whom the message should be associated with
      */
-    fun printMessage(kind: Diagnostic.Kind, msg: String, element: XElement? = null)
+    final fun printMessage(kind: Diagnostic.Kind, msg: String, element: XElement? = null) {
+        watchers.forEach {
+            it.printMessage(kind, msg, element)
+        }
+        onPrintMessage(kind, msg, element)
+    }
+
+    abstract fun onPrintMessage(kind: Diagnostic.Kind, msg: String, element: XElement? = null)
+
+    fun addMessageWatcher(watcher: XMessager) {
+        watchers.add(watcher)
+    }
+
+    fun removeMessageWatcher(watcher: XMessager) {
+        watchers.remove(watcher)
+    }
 }
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnvMessager.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnvMessager.kt
index f5120fa..e49a5f7 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnvMessager.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnvMessager.kt
@@ -27,8 +27,8 @@
 
 internal class JavacProcessingEnvMessager(
     private val processingEnv: ProcessingEnvironment
-) : XMessager {
-    override fun printMessage(kind: Diagnostic.Kind, msg: String, element: XElement?) {
+) : XMessager() {
+    override fun onPrintMessage(kind: Diagnostic.Kind, msg: String, element: XElement?) {
         val javacElement = (element as? JavacElement)?.element
         processingEnv.messager.printMessage(
             kind,
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMessager.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMessager.kt
index 557cfa3..1497d90 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMessager.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMessager.kt
@@ -23,8 +23,8 @@
 
 internal class KspMessager(
     private val logger: KSPLogger
-) : XMessager {
-    override fun printMessage(kind: Diagnostic.Kind, msg: String, element: XElement?) {
+) : XMessager() {
+    override fun onPrintMessage(kind: Diagnostic.Kind, msg: String, element: XElement?) {
         val ksNode = (element as? KspElement)?.declaration
         when (kind) {
             Diagnostic.Kind.ERROR -> logger.error(msg, ksNode)
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
index 49ad742..d381c20 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
@@ -316,8 +316,7 @@
     ): List<String> {
         lateinit var result: List<String>
         runKaptTest(
-            sources = listOf(source),
-            succeed = true
+            sources = listOf(source)
         ) { invocation ->
             val (target, methods) = invocation.getOverrideTestTargets(
                 ignoreInheritedMethods
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt
index 1f3260d..8296f70 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XArrayTypeTest.kt
@@ -170,8 +170,7 @@
     @Test
     fun createArray() {
         runKspTest(
-            sources = emptyList(),
-            succeed = true
+            sources = emptyList()
         ) { invocation ->
             val intType = invocation.processingEnv.requireType("kotlin.Int")
             invocation.processingEnv.getArrayType(intType).let {
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
index 4ae8c01..2232305 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
@@ -17,7 +17,7 @@
 package androidx.room.compiler.processing
 
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTestForFailedCompilation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.compiler.processing.util.runProcessorTestIncludingKsp
 import com.google.common.truth.Truth.assertThat
 import com.squareup.javapoet.ClassName
@@ -224,13 +224,18 @@
             """.trimIndent()
         )
         // TODO include KSP when https://github.com/google/ksp/issues/122 is fixed.
-        runProcessorTestForFailedCompilation(
+        runProcessorTest(
             sources = listOf(src)
         ) {
             it.processingEnv.messager.printMessage(
                 Diagnostic.Kind.ERROR,
                 "intentional failure"
             )
+            it.assertCompilationResult {
+                compilationDidFail()
+                    .and()
+                    .hasError("intentional failure")
+            }
         }
     }
 
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index 2cd2588..c7cd991 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -25,7 +25,6 @@
 import androidx.room.compiler.processing.util.kspResolver
 import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
-import androidx.room.compiler.processing.util.runProcessorTestForFailedCompilation
 import androidx.room.compiler.processing.util.runProcessorTestIncludingKsp
 import androidx.room.compiler.processing.util.typeName
 import com.google.common.truth.Truth
@@ -111,7 +110,7 @@
             """.trimIndent()
         )
         // TODO run with KSP as well once https://github.com/google/ksp/issues/107 is resolved
-        runProcessorTestForFailedCompilation(
+        runProcessorTest(
             sources = listOf(missingTypeRef)
         ) {
             val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
@@ -127,6 +126,9 @@
                     ClassName.get("", "NotExistingType")
                 )
             }
+            it.assertCompilationResult {
+                compilationDidFail()
+            }
         }
     }
 
@@ -174,7 +176,7 @@
 
     @Test
     fun isCollection_kotlin() {
-        runKspTest(sources = emptyList(), succeed = true) { invocation ->
+        runKspTest(sources = emptyList()) { invocation ->
             val subjects = listOf("Map" to false, "List" to true, "Set" to true)
             subjects.forEach { (subject, expected) ->
                 invocation.processingEnv.requireType("kotlin.collections.$subject").let { type ->
@@ -213,11 +215,14 @@
             """.trimIndent()
         )
         // TODO run with KSP as well once https://github.com/google/ksp/issues/107 is resolved
-        runProcessorTestForFailedCompilation(
+        runProcessorTest(
             sources = listOf(missingTypeRef)
         ) {
             val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
             assertThat(element.superType?.isError()).isTrue()
+            it.assertCompilationResult {
+                compilationDidFail()
+            }
         }
     }
 
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSAsMemberOfTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSAsMemberOfTest.kt
index 1bc7787..5cfa2d6 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSAsMemberOfTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSAsMemberOfTest.kt
@@ -48,7 +48,7 @@
             """.trimIndent()
         )
 
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             val base = invocation.processingEnv.requireTypeElement("BaseClass")
             val sub = invocation.processingEnv.requireType("SubClass").asDeclaredType()
             base.getField("normalInt").let { prop ->
@@ -121,7 +121,7 @@
             abstract class NullableSubject: MyInterface<String?>()
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             val myInterface = invocation.processingEnv.requireTypeElement("MyInterface")
             val nonNullSubject = invocation.processingEnv.requireType("NonNullSubject")
                 .asDeclaredType()
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt
index c185f47..2865715 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KSTypeExtTest.kt
@@ -26,7 +26,6 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.devtools.ksp.getDeclaredFunctions
 import com.google.devtools.ksp.getDeclaredProperties
-import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.symbol.KSClassDeclaration
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.ParameterizedTypeName
@@ -55,18 +54,18 @@
             }
             """.trimIndent()
         )
-        runTest(subjectSrc) { resolver ->
-            val subject = resolver.requireClass("foo.bar.Baz")
-            assertThat(subject.propertyType("intField").typeName(resolver))
+        runKspTest(sources = listOf(subjectSrc)) { invocation ->
+            val subject = invocation.kspResolver.requireClass("foo.bar.Baz")
+            assertThat(subject.propertyType("intField").typeName(invocation.kspResolver))
                 .isEqualTo(TypeName.INT)
-            assertThat(subject.propertyType("listOfInts").typeName(resolver))
+            assertThat(subject.propertyType("listOfInts").typeName(invocation.kspResolver))
                 .isEqualTo(
                     ParameterizedTypeName.get(
                         List::class.className(),
                         TypeName.INT.box()
                     )
                 )
-            assertThat(subject.propertyType("mutableMapOfAny").typeName(resolver))
+            assertThat(subject.propertyType("mutableMapOfAny").typeName(invocation.kspResolver))
                 .isEqualTo(
                     ParameterizedTypeName.get(
                         Map::class.className(),
@@ -74,7 +73,7 @@
                         TypeName.OBJECT,
                     )
                 )
-            val typeName = subject.propertyType("nested").typeName(resolver)
+            val typeName = subject.propertyType("nested").typeName(invocation.kspResolver)
             check(typeName is ClassName)
             assertThat(typeName.packageName()).isEqualTo("foo.bar")
             assertThat(typeName.simpleNames()).containsExactly("Baz", "Nested")
@@ -97,22 +96,25 @@
             }
             """.trimIndent()
         )
-        runTest(subjectSrc) { resolver ->
-            val subject = resolver.requireClass("Baz")
-            assertThat(subject.propertyType("intField").typeName(resolver))
-                .isEqualTo(TypeName.INT)
-            assertThat(subject.propertyType("listOfInts").typeName(resolver))
-                .isEqualTo(
-                    ParameterizedTypeName.get(
-                        List::class.className(),
-                        TypeName.INT.box()
-                    )
+        runKspTest(sources = listOf(subjectSrc)) { invocation ->
+            val subject = invocation.kspResolver.requireClass("Baz")
+            assertThat(
+                subject.propertyType("intField").typeName(invocation.kspResolver)
+            ).isEqualTo(TypeName.INT)
+            assertThat(
+                subject.propertyType("listOfInts").typeName(invocation.kspResolver)
+            ).isEqualTo(
+                ParameterizedTypeName.get(
+                    List::class.className(),
+                    TypeName.INT.box()
                 )
-            assertThat(subject.propertyType("incompleteGeneric").typeName(resolver))
-                .isEqualTo(
-                    List::class.className()
-                )
-            assertThat(subject.propertyType("nested").typeName(resolver))
+            )
+            assertThat(
+                subject.propertyType("incompleteGeneric").typeName(invocation.kspResolver)
+            ).isEqualTo(
+                List::class.className()
+            )
+            assertThat(subject.propertyType("nested").typeName(invocation.kspResolver))
                 .isEqualTo(
                     ClassName.get("", "Baz", "Nested")
                 )
@@ -132,25 +134,31 @@
             }
             """.trimIndent()
         )
-        runTest(subjectSrc, succeed = false) { resolver ->
-            val subject = resolver.requireClass("Foo")
-            assertThat(subject.propertyType("errorField").typeName(resolver))
-                .isEqualTo(ERROR_TYPE_NAME)
-            assertThat(subject.propertyType("listOfError").typeName(resolver))
-                .isEqualTo(
-                    ParameterizedTypeName.get(
-                        List::class.className(),
-                        ERROR_TYPE_NAME
-                    )
+        runKspTest(sources = listOf(subjectSrc)) { invocation ->
+            val subject = invocation.kspResolver.requireClass("Foo")
+            assertThat(
+                subject.propertyType("errorField").typeName(invocation.kspResolver)
+            ).isEqualTo(ERROR_TYPE_NAME)
+            assertThat(
+                subject.propertyType("listOfError").typeName(invocation.kspResolver)
+            ).isEqualTo(
+                ParameterizedTypeName.get(
+                    List::class.className(),
+                    ERROR_TYPE_NAME
                 )
-            assertThat(subject.propertyType("mutableMapOfDontExist").typeName(resolver))
-                .isEqualTo(
-                    ParameterizedTypeName.get(
-                        Map::class.className(),
-                        String::class.className(),
-                        ERROR_TYPE_NAME
-                    )
+            )
+            assertThat(
+                subject.propertyType("mutableMapOfDontExist").typeName(invocation.kspResolver)
+            ).isEqualTo(
+                ParameterizedTypeName.get(
+                    Map::class.className(),
+                    String::class.className(),
+                    ERROR_TYPE_NAME
                 )
+            )
+            invocation.assertCompilationResult {
+                compilationDidFail()
+            }
         }
     }
 
@@ -181,8 +189,7 @@
         // methodName -> returnType, ...paramTypes
         val golden = mutableMapOf<String, List<TypeName>>()
         runKaptTest(
-            sources = listOf(src),
-            succeed = true
+            sources = listOf(src)
         ) { invocation ->
             val env = (invocation.processingEnv as JavacProcessingEnv)
             val subject = env.delegate.elementUtils.getTypeElement("Subject")
@@ -196,8 +203,7 @@
         }
         val kspResults = mutableMapOf<String, List<TypeName>>()
         runKspTest(
-            sources = listOf(src),
-            succeed = true
+            sources = listOf(src)
         ) { invocation ->
             val env = (invocation.processingEnv as KspProcessingEnv)
             val subject = env.resolver.requireClass("Subject")
@@ -220,21 +226,6 @@
         assertThat(kspResults).containsExactlyEntriesIn(golden)
     }
 
-    private fun runTest(
-        vararg sources: Source,
-        succeed: Boolean = true,
-        handler: (Resolver) -> Unit
-    ) {
-        runKspTest(
-            sources = sources.toList(),
-            succeed = succeed
-        ) {
-            handler(
-                (it.processingEnv as KspProcessingEnv).resolver
-            )
-        }
-    }
-
     private fun KSClassDeclaration.requireProperty(name: String) = getDeclaredProperties().first {
         it.simpleName.asString() == name
     }
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeElementTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeElementTest.kt
index 437bdc3..9c295b1 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeElementTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeElementTest.kt
@@ -52,8 +52,7 @@
             """.trimIndent()
         )
         runKspTest(
-            sources = listOf(src1, src2),
-            succeed = true
+            sources = listOf(src1, src2)
         ) { invocation ->
             invocation.processingEnv.requireTypeElement("TopLevel").let {
                 assertThat(it.packageName).isEqualTo("")
@@ -93,7 +92,7 @@
             interface MyInterface {}
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             invocation.processingEnv.requireTypeElement("foo.bar.Baz").let {
                 assertThat(it.superType).isEqualTo(
                     invocation.processingEnv.requireType("foo.bar.AbstractClass")
@@ -134,7 +133,7 @@
             }
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             invocation.processingEnv.requireTypeElement("foo.bar.Outer").let {
                 assertThat(it.className).isEqualTo(ClassName.get("foo.bar", "Outer"))
                 assertThat(it.enclosingTypeElement).isNull()
@@ -163,7 +162,7 @@
             private class PrivateClass
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             fun getModifiers(element: XTypeElement): Set<String> {
                 val result = mutableSetOf<String>()
                 if (element.isAbstract()) result.add("abstract")
@@ -205,7 +204,7 @@
             interface MyInterface
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             invocation.processingEnv.requireTypeElement("MyClass").let {
                 assertThat(it.kindName()).isEqualTo("class")
             }
@@ -269,7 +268,7 @@
             ) : BaseClass(value)
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             val baseClass = invocation.processingEnv.requireTypeElement("BaseClass")
             assertThat(baseClass.getAllFieldNames()).containsExactly("value")
             val subClass = invocation.processingEnv.requireTypeElement("SubClass")
@@ -317,7 +316,7 @@
             }
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             val base = invocation.processingEnv.requireTypeElement("Base")
             assertThat(base.getDeclaredMethods().names()).containsExactly(
                 "baseFun", "suspendFun", "privateBaseFun", "staticBaseFun"
@@ -371,7 +370,7 @@
             }
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             val klass = invocation.processingEnv.requireTypeElement("SubClass")
             assertThat(klass.getAllMethods().names()).containsExactly(
                 "baseMethod", "overriddenMethod", "baseCompanionMethod",
@@ -395,7 +394,7 @@
             }
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             invocation.processingEnv.requireTypeElement("JustGetter").let { base ->
                 assertThat(base.getDeclaredMethods().names()).containsExactly(
                     "getX"
@@ -438,7 +437,7 @@
             class SubClass : CompanionSubject()
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("CompanionSubject")
             assertThat(subject.getAllFieldNames()).containsExactly(
                 "mutableStatic", "immutableStatic"
@@ -471,7 +470,7 @@
             }
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             invocation.processingEnv.requireTypeElement("JustGetter").let { base ->
                 assertThat(base.getDeclaredMethods().names()).containsExactly(
                     "getX"
@@ -576,7 +575,7 @@
             }
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("MyInterface")
             assertThat(subject.getMethod("notJvmDefault").isJavaDefault()).isFalse()
             assertThat(subject.getMethod("jvmDefault").isJavaDefault()).isTrue()
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
index db0fc7dc..7643b04 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
@@ -50,7 +50,7 @@
             interface MyInterface {}
             """.trimIndent()
         )
-        runKspTest(listOf(src), succeed = true) {
+        runKspTest(listOf(src)) {
             val subject = it.processingEnv.requireType("foo.bar.Baz")
             assertThat(subject.typeName).isEqualTo(
                 ClassName.get("foo.bar", "Baz")
@@ -89,8 +89,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = false
+            listOf(src)
         ) { invocation ->
             invocation.requireDeclaredPropertyType("errorType").let { type ->
                 assertThat(type.isError()).isTrue()
@@ -107,6 +106,9 @@
                     assertThat(typeArg.typeName).isEqualTo(ERROR_TYPE_NAME)
                 }
             }
+            invocation.assertCompilationResult {
+                compilationDidFail()
+            }
         }
     }
 
@@ -121,8 +123,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = true
+            listOf(src)
         ) { invocation ->
             invocation.requireDeclaredPropertyType("listOfNullableStrings").let { type ->
                 assertThat(type.nullability).isEqualTo(NONNULL)
@@ -173,8 +174,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = true
+            listOf(src)
         ) { invocation ->
             val nullableStringList = invocation
                 .requireDeclaredPropertyType("listOfNullableStrings")
@@ -220,8 +220,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = true
+            listOf(src)
         ) { invocation ->
             invocation.requirePropertyType("simple").let {
                 assertThat(it.rawType.typeName).isEqualTo(TypeName.INT)
@@ -257,7 +256,7 @@
             }
             """.trimIndent()
         )
-        runKspTest(sources = listOf(src), succeed = true) { invocation ->
+        runKspTest(sources = listOf(src)) { invocation ->
             val resolver = (invocation.processingEnv as KspProcessingEnv).resolver
             val voidMethod = resolver.getClassDeclarationByName("foo.bar.Baz")!!
                 .getDeclaredFunctions()
@@ -289,8 +288,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = false
+            listOf(src)
         ) { invocation ->
             fun mapProp(name: String) = invocation.requirePropertyType(name).let {
                 listOf(
@@ -313,6 +311,9 @@
             assertThat(mapProp("nullableByteProp")).containsExactly("isByte")
             assertThat(mapProp("errorProp")).containsExactly("isError")
             assertThat(mapProp("nullableErrorProp")).containsExactly("isError")
+            invocation.assertCompilationResult {
+                compilationDidFail()
+            }
         }
     }
 
@@ -334,8 +335,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = false
+            listOf(src)
         ) { invocation ->
             fun getDefaultValue(name: String) = invocation.requirePropertyType(name).defaultValue()
             // javac types do not check nullability but checking it is more correct
@@ -351,6 +351,9 @@
             assertThat(getDefaultValue("errorProp")).isEqualTo("null")
             assertThat(getDefaultValue("nullableErrorProp")).isEqualTo("null")
             assertThat(getDefaultValue("stringProp")).isEqualTo("null")
+            invocation.assertCompilationResult {
+                compilationDidFail()
+            }
         }
     }
 
@@ -366,8 +369,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = true
+            listOf(src)
         ) { invocation ->
             assertThat(
                 invocation.requirePropertyType("stringProp").isTypeOf(
@@ -418,8 +420,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = true
+            listOf(src)
         ) { invocation ->
             fun check(prop1: String, prop2: String): Boolean {
                 return invocation.requirePropertyType(prop1).isSameType(
@@ -450,8 +451,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = true
+            listOf(src)
         ) { invocation ->
             val env = (invocation.processingEnv as KspProcessingEnv)
             val classNames = listOf("Bar", "Bar_NullableFoo")
@@ -491,8 +491,7 @@
             """.trimIndent()
         )
         runKspTest(
-            listOf(src),
-            succeed = true
+            listOf(src)
         ) { invocation ->
             val env = (invocation.processingEnv as KspProcessingEnv)
             val method = env.resolver
diff --git a/room/compiler/src/main/kotlin/androidx/room/log/RLog.kt b/room/compiler/src/main/kotlin/androidx/room/log/RLog.kt
index d18778e..0ce7b73 100644
--- a/room/compiler/src/main/kotlin/androidx/room/log/RLog.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/log/RLog.kt
@@ -75,9 +75,9 @@
         messager.printMessage(WARNING, msg.safeFormat(args), defaultElement)
     }
 
-    class CollectingMessager : XMessager {
+    class CollectingMessager : XMessager() {
         private val messages = mutableMapOf<Diagnostic.Kind, MutableList<Pair<String, XElement?>>>()
-        override fun printMessage(kind: Diagnostic.Kind, msg: String, element: XElement?) {
+        override fun onPrintMessage(kind: Diagnostic.Kind, msg: String, element: XElement?) {
             messages.getOrPut(
                 kind,
                 {
diff --git a/room/compiler/src/test/kotlin/androidx/room/log/RLogTest.kt b/room/compiler/src/test/kotlin/androidx/room/log/RLogTest.kt
index b87099d..71a4ce0 100644
--- a/room/compiler/src/test/kotlin/androidx/room/log/RLogTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/log/RLogTest.kt
@@ -16,20 +16,22 @@
 
 package androidx.room.log
 
+import androidx.room.compiler.processing.XElement
 import androidx.room.compiler.processing.XMessager
 import androidx.room.vo.Warning
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
+import javax.tools.Diagnostic
 
 @RunWith(JUnit4::class)
 class RLogTest {
-
-    val messager = mock(XMessager::class.java)
-
     @Test
     fun testSafeFormat() {
+        val messager = object : XMessager() {
+            override fun onPrintMessage(kind: Diagnostic.Kind, msg: String, element: XElement?) {
+            }
+        }
         val logger = RLog(messager, emptySet(), null)
 
         // UnknownFormatConversionException