1 /* <lambda>null2 * Copyright 2023 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.build.clang 18 19 import androidx.testutils.assertThrows 20 import com.google.common.truth.Truth.assertThat 21 import com.google.common.truth.Truth.assertWithMessage 22 import java.io.File 23 import org.gradle.api.GradleException 24 import org.gradle.api.file.DirectoryProperty 25 import org.jetbrains.kotlin.konan.file.use 26 import org.jetbrains.kotlin.konan.target.Family 27 import org.jetbrains.kotlin.konan.target.KonanTarget 28 import org.junit.Before 29 import org.junit.Test 30 31 class KonanBuildServiceTest : BaseClangTest() { 32 private lateinit var buildService: KonanBuildService 33 34 @Before 35 fun initBuildService() { 36 buildService = KonanBuildService.obtain(project).get() 37 } 38 39 @Test 40 fun compilationFailure() { 41 val compileParams = 42 createCompileParameters( 43 "failedCode.c", 44 """ 45 #include <stdio.h> 46 int main() { 47 printf("Hello, World!"); 48 return 0 // no ; :) 49 } 50 """ 51 .trimIndent() 52 ) 53 assertThrows<GradleException> { buildService.compile(compileParams) } 54 .hasMessageThat() 55 .contains("expected ';' after return statement") 56 } 57 58 @Test 59 fun compile() { 60 val compileParams = createCompileParameters("code.c", C_HELLO_WORLD) 61 buildService.compile(compileParams) 62 val outputFiles = compileParams.output.getRegularFiles() 63 assertThat(outputFiles).hasSize(1) 64 val outputFile = outputFiles.single() 65 assertThat(outputFile.name).isEqualTo("code.o") 66 val strings = extractStrings(outputFile) 67 assertThat(strings).contains("Hello, World!!") 68 // shouldn't link yet 69 assertThat(strings).doesNotContain("libc") 70 } 71 72 @Test 73 fun compileWithInclude() { 74 val compileParameters = 75 createCompileParameters( 76 "code.c", 77 """ 78 #include <stdio.h> 79 #include "dependency.h" 80 int my_function() { 81 return dependency_method(); 82 } 83 """ 84 .trimIndent() 85 ) 86 val dependency = 87 tmpFolder.newFolder("depSrc").also { 88 it.resolve("dependency.h") 89 .writeText( 90 """ 91 int dependency_method(); 92 """ 93 .trimIndent() 94 ) 95 } 96 compileParameters.includes.from(dependency) 97 buildService.compile(compileParameters) 98 val outputFiles = compileParameters.output.getRegularFiles() 99 val strings = extractStrings(outputFiles.single()) 100 assertThat(strings).contains("dependency_method") 101 } 102 103 @Test 104 fun createSharedLibrary() { 105 val compileParameters = createCompileParameters("code.c", C_HELLO_WORLD) 106 buildService.compile(compileParameters) 107 val sharedLibraryParameters = 108 project.objects.newInstance(ClangSharedLibraryParameters::class.java) 109 sharedLibraryParameters.konanTarget.set(compileParameters.konanTarget) 110 sharedLibraryParameters.objectFiles.from(compileParameters.output) 111 val outputFile = tmpFolder.newFile("code.so") 112 sharedLibraryParameters.outputFile.set(outputFile) 113 buildService.createSharedLibrary(sharedLibraryParameters) 114 115 val strings = extractStrings(outputFile) 116 assertThat(strings).contains("Hello, World!!") 117 // should link with libc 118 assertThat(strings).contains("libc") 119 120 // verify shared lib files are aligned to 16Kb boundary for Android targets 121 if (sharedLibraryParameters.konanTarget.get().asKonanTarget.family == Family.ANDROID) { 122 val alignment = 123 ProcessBuilder("objdump", "-p", outputFile.path) 124 .start() 125 .inputStream 126 .bufferedReader() 127 .useLines { lines -> 128 lines 129 .filter { it.contains("LOAD") } 130 .map { it.split(" ").last() } 131 .firstOrNull() 132 } 133 assertThat(alignment).isEqualTo("2**14") 134 } 135 } 136 137 @Test 138 fun archive() { 139 val compileParams = createCompileParameters("code.c", C_HELLO_WORLD) 140 buildService.compile(compileParams) 141 val archiveParams = project.objects.newInstance(ClangArchiveParameters::class.java) 142 archiveParams.konanTarget.set(compileParams.konanTarget) 143 archiveParams.objectFiles.from(compileParams.output) 144 val outputFile = tmpFolder.newFile("code.a") 145 archiveParams.outputFile.set(outputFile) 146 buildService.archiveLibrary(archiveParams) 147 148 val strings = extractStrings(outputFile) 149 assertThat(strings).contains("Hello, World!!") 150 // should not with libc 151 assertThat(strings).doesNotContain("libc") 152 } 153 154 private fun createCompileParameters(fileName: String, code: String): ClangCompileParameters { 155 val srcDir = tmpFolder.newFolder("src") 156 srcDir.resolve(fileName).writeText(code) 157 val compileParams = project.objects.newInstance(ClangCompileParameters::class.java) 158 compileParams.konanTarget.set(SerializableKonanTarget(KonanTarget.LINUX_X64)) 159 compileParams.output.set(tmpFolder.newFolder()) 160 compileParams.sources.from(srcDir) 161 return compileParams 162 } 163 164 private fun DirectoryProperty.getRegularFiles() = 165 get().asFile.walkTopDown().filter { it.isFile }.toList() 166 167 /** 168 * Extract strings from a binary file so that we can assert output contents. 169 * 170 * We used to use linux strings command here but it stopped working in CI. This implementation 171 * pretty much matches our strings usage and good enough for these tests. 172 * https://man7.org/linux/man-pages/man1/strings.1.html 173 */ 174 private fun extractStrings(file: File): String { 175 assertWithMessage("Cannot extract strings from file").that(file.isFile).isTrue() 176 val finalString = StringBuilder() 177 val currentSection = StringBuilder() 178 fun finishSection() { 179 if (currentSection.length > 4) { 180 finalString.appendLine(currentSection) 181 } 182 currentSection.setLength(0) 183 } 184 file.inputStream().buffered(1024).use { inputStream -> 185 var byte: Int 186 do { 187 byte = inputStream.read() 188 // if it is a printable string, add it to the list. 189 if (byte in 32..127) { 190 currentSection.append(byte.toChar()) 191 } else { 192 // cleanup the remaining 193 finishSection() 194 } 195 } while (byte != -1) 196 } 197 // one final cleanup 198 finishSection() 199 return finalString.toString() 200 } 201 202 companion object { 203 private val C_HELLO_WORLD = 204 """ 205 #include <stdio.h> 206 int my_function() { 207 printf("Hello, World!!"); 208 return 0; 209 } 210 """ 211 .trimIndent() 212 } 213 } 214