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