1 /*
<lambda>null2 * Copyright 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.privacysandbox.tools.testing
18
19 import androidx.room.compiler.processing.util.DiagnosticLocation
20 import androidx.room.compiler.processing.util.DiagnosticMessage
21 import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
22 import androidx.room.compiler.processing.util.Source
23 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
24 import androidx.room.compiler.processing.util.compiler.TestCompilationResult
25 import androidx.room.compiler.processing.util.compiler.compile
26 import com.google.common.truth.Truth.assertThat
27 import com.google.common.truth.Truth.assertWithMessage
28 import com.google.devtools.ksp.processing.SymbolProcessorProvider
29 import java.io.File
30 import java.nio.file.Files
31 import javax.tools.Diagnostic
32
33 object CompilationTestHelper {
34 fun assertCompiles(sources: List<Source>): TestCompilationResult {
35 val result = compileAll(sources)
36 assertThat(result).succeedsExcludingOptInWarnings()
37 return result
38 }
39
40 fun compileAll(
41 sources: List<Source>,
42 extraClasspath: List<File> = emptyList(),
43 symbolProcessorProviders: List<SymbolProcessorProvider> = emptyList(),
44 processorOptions: Map<String, String> = emptyMap(),
45 ): TestCompilationResult {
46 val tempDir = Files.createTempDirectory("compile").toFile().also { it.deleteOnExit() }
47 return compile(
48 tempDir,
49 TestCompilationArguments(
50 sources = sources,
51 classpath = extraClasspath,
52 symbolProcessorProviders = symbolProcessorProviders,
53 processorOptions = processorOptions,
54 kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
55 )
56 )
57 }
58
59 fun assertThat(result: TestCompilationResult) = CompilationResultSubject(result)
60 }
61
62 val TestCompilationResult.resourceOutputDir: File
63 get() = outputClasspath.first().parentFile.resolve("ksp-compiler/resourceOutputDir")
64
hasAllExpectedGeneratedSourceFilesAndContentnull65 fun hasAllExpectedGeneratedSourceFilesAndContent(
66 actualKotlinSources: List<Source>,
67 expectedKotlinSources: List<Source>,
68 expectedAidlFilepath: List<String>
69 ) {
70 val expectedRelativePaths =
71 expectedKotlinSources.map(Source::relativePath) + expectedAidlFilepath
72 assertThat(actualKotlinSources.map(Source::relativePath))
73 .containsExactlyElementsIn(expectedRelativePaths)
74
75 val actualRelativePathMap = actualKotlinSources.associateBy(Source::relativePath)
76 for (expectedKotlinSource in expectedKotlinSources) {
77 assertWithMessage(
78 "Contents of generated file ${expectedKotlinSource.relativePath} don't " +
79 "match golden."
80 )
81 .that(actualRelativePathMap[expectedKotlinSource.relativePath]?.contents)
82 .isEqualTo(expectedKotlinSource.contents)
83 }
84 }
85
86 class CompilationResultSubject(private val result: TestCompilationResult) {
succeedsnull87 fun succeeds() {
88 assertWithMessage("Unexpected errors:\n${getFullErrorMessages().joinToString("\n")}")
89 .that(result.success && getRawErrorMessages().isEmpty())
90 .isTrue()
91 }
92
93 // TODO(b/388455777): remove and switch back to using 'succeeds()' once annotation generation is
94 // added to the library.
succeedsExcludingOptInWarningsnull95 fun succeedsExcludingOptInWarnings() {
96 assertWithMessage("Unexpected errors:\n${getFullErrorMessages().joinToString("\n")}")
97 .that(
98 result.success &&
99 getRawErrorMessages()
100 .filterNot { message ->
101 message.kind == Diagnostic.Kind.WARNING &&
102 message.msg.contains(
103 "This API is experimental. It may be changed in the future without notice."
104 )
105 }
106 .isEmpty()
107 )
108 .isTrue()
109 }
110
hasAllExpectedGeneratedSourceFilesAndContentnull111 fun hasAllExpectedGeneratedSourceFilesAndContent(
112 expectedKotlinSources: List<Source>,
113 expectedAidlFilepath: List<String>
114 ) {
115 hasAllExpectedGeneratedSourceFilesAndContent(
116 result.generatedSources,
117 expectedKotlinSources,
118 expectedAidlFilepath
119 )
120 }
121
hasNoGeneratedSourceFilesnull122 fun hasNoGeneratedSourceFiles() {
123 assertThat(result.generatedSources).isEmpty()
124 }
125
failsnull126 fun fails() {
127 assertThat(result.success).isFalse()
128 }
129
containsErrornull130 fun containsError(error: String) {
131 assertThat(getShortErrorMessages()).contains(error)
132 }
133
containsExactlyErrorsnull134 fun containsExactlyErrors(vararg errors: String) {
135 assertThat(getShortErrorMessages()).containsExactly(*errors)
136 }
137
getRawErrorMessagesnull138 private fun getRawErrorMessages(): List<DiagnosticMessage> {
139 return (result.diagnostics[Diagnostic.Kind.ERROR] ?: emptyList()) +
140 (result.diagnostics[Diagnostic.Kind.WARNING] ?: emptyList()) +
141 (result.diagnostics[Diagnostic.Kind.MANDATORY_WARNING] ?: emptyList())
142 }
143
getShortErrorMessagesnull144 private fun getShortErrorMessages() =
145 result.diagnostics[Diagnostic.Kind.ERROR]?.map(DiagnosticMessage::msg)
146
147 private fun getFullErrorMessages() = getRawErrorMessages().map { it.toFormattedMessage() }
148
DiagnosticMessagenull149 private fun DiagnosticMessage.toFormattedMessage() =
150 """
151 |$kind: $msg
152 |${location?.toFormattedLocation()}$
153 """
154 .trimMargin()
155
156 private fun DiagnosticLocation.toFormattedLocation(): String {
157 if (source == null) return "Location information missing"
158 return """
159 |Location: ${source!!.relativePath}:$line
160 |File:
161 |${contentsHighlightingLine(source!!.contents, line)}
162 """
163 .trimMargin()
164 }
165
contentsHighlightingLinenull166 private fun contentsHighlightingLine(contents: String, line: Int): String {
167 var lineCountdown = line
168 // Insert a "->" in the beginning of the highlighted line.
169 return contents.split("\n").joinToString("\n") {
170 val lineHeader = if (--lineCountdown == 0) "->" else " "
171 "$lineHeader$it"
172 }
173 }
174 }
175