[KLIB] ABI reader: Refactor manifest reading test

^KT-54402
This commit is contained in:
Dmitriy Dolovov
2023-07-27 17:24:58 +02:00
committed by Space Team
parent ac3c000ab1
commit a95f963ad3
3 changed files with 102 additions and 207 deletions
@@ -0,0 +1,102 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.library.abi
import com.intellij.openapi.util.io.FileUtil.createTempDirectory
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutForWriter
import org.jetbrains.kotlin.library.impl.KotlinLibraryWriterImpl
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo
import java.io.File
import java.util.*
import org.jetbrains.kotlin.konan.file.File as KFile
@OptIn(ExperimentalLibraryAbiReader::class)
class ManifestReadingTest {
private lateinit var buildDir: File
@BeforeEach
fun setUp(testInfo: TestInfo) {
buildDir = createTempDirectory(testInfo.testClass.get().simpleName, testInfo.testMethod.get().name)
}
@AfterEach
fun tearDown() {
buildDir.deleteRecursively()
}
@Test
fun testManifestReading() {
val testData = mapOf(
"sample-library-1" to LibraryManifest(
platform = BuiltInsPlatform.JS.name,
nativeTargets = emptyList(),
compilerVersion = "1.23.45",
abiVersion = "2.34.56",
libraryVersion = "3.45.67",
irProviderName = "test_ir_provider_123"
),
"sample-library-2" to LibraryManifest(
platform = BuiltInsPlatform.NATIVE.name,
nativeTargets = listOf("ios_arm64", "ios_simulator_arm64", "macos_arm64", "macos_x64"),
compilerVersion = null,
abiVersion = null,
libraryVersion = null,
irProviderName = null
),
)
testData.forEach { (libraryName, originalManifest) ->
val libraryFile = createEmptyLibraryWithSpecificManifest(libraryName, originalManifest)
val readManifest = LibraryAbiReader.readAbiInfo(libraryFile).manifest
assertTrue(originalManifest !== readManifest) { "Library name: $libraryName" }
assertEquals(originalManifest, readManifest) { "Library name: $libraryName" }
}
}
private fun createEmptyLibraryWithSpecificManifest(libraryName: String, libraryManifest: LibraryManifest): File {
val libraryVersioning = KotlinLibraryVersioning(
libraryVersion = libraryManifest.libraryVersion,
compilerVersion = libraryManifest.compilerVersion,
abiVersion = libraryManifest.abiVersion?.parseKotlinAbiVersion(),
metadataVersion = null
)
val builtInsPlatform = libraryManifest.platform?.let(BuiltInsPlatform::parseFromString)
?: error("Unknown platform: ${libraryManifest.platform}")
val libraryFile = buildDir.resolve("$libraryName.klib")
val libraryKFile = KFile(libraryFile.absolutePath)
val libraryLayout = KotlinLibraryLayoutForWriter(libraryKFile, libraryKFile)
val library = KotlinLibraryWriterImpl(
moduleName = libraryName,
versions = libraryVersioning,
builtInsPlatform = builtInsPlatform,
nativeTargets = libraryManifest.nativeTargets,
nopack = true,
shortName = libraryName,
layout = libraryLayout
)
libraryManifest.irProviderName?.let { irProviderName ->
library.addManifestAddend(
Properties().apply {
this[KLIB_PROPERTY_IR_PROVIDER] = irProviderName
}
)
}
library.addIr(SerializedIrModule(files = emptyList())) // Empty library.
library.commit()
return libraryFile
}
}
@@ -1,45 +0,0 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.library.abi
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo
import java.io.File
@OptIn(ExperimentalLibraryAbiReader::class)
class ManifestRenderingTest {
private lateinit var buildDir: File
private lateinit var sourceFile: File
@BeforeEach
fun setUp(testInfo: TestInfo) {
buildDir = setUpBuildDir(testInfo)
sourceFile = buildDir.resolve("source.kt").apply { createNewFile() } // Just an empty file.
}
@Test
fun renderWithManifest() {
val customManifest = LibraryManifest(
platform = BuiltInsPlatform.JS.name,
nativeTargets = listOf("ios_arm64", "ios_simulator_arm64", "macos_arm64", "macos_x64"),
compilerVersion = "1.23.45",
abiVersion = "2.34.56",
libraryVersion = "3.45.67",
irProviderName = "test_ir_provider_123"
)
val libraryFile = buildLibrary(sourceFile, libraryName = "sample-library", buildDir)
patchManifest(libraryFile, customManifest)
val readManifest = LibraryAbiReader.readAbiInfo(libraryFile).manifest
assertTrue(customManifest !== readManifest)
assertEquals(customManifest, readManifest)
}
}
@@ -1,162 +0,0 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.library.abi
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.local.CoreLocalFileSystem
import com.intellij.psi.PsiManager
import com.intellij.psi.SingleRootFileViewProvider
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.js.klib.compileModuleToAnalyzedFirWithPsi
import org.jetbrains.kotlin.cli.js.klib.serializeFirKlib
import org.jetbrains.kotlin.cli.js.klib.transformFirToIr
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.Companion.createForParallelTests
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
import org.jetbrains.kotlin.ir.backend.js.MainModule
import org.jetbrains.kotlin.ir.backend.js.ModulesStructure
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.konan.file.ZipFileSystemCacheableAccessor
import org.jetbrains.kotlin.konan.file.unzipTo
import org.jetbrains.kotlin.konan.file.zipDirAs
import org.jetbrains.kotlin.konan.properties.saveToFile
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.test.utils.TestDisposable
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.TestInfo
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import java.nio.charset.Charset
import org.jetbrains.kotlin.konan.file.File as KFile
private val TestInfo.className: String get() = testClass.orElseGet { fail("Can't get test class name") }.simpleName
private val TestInfo.methodName: String get() = testMethod.orElseGet { fail("Can't get test method name") }.name
internal fun setUpBuildDir(testInfo: TestInfo): File {
val projectBuildDir = System.getenv(ENV_VAR_PROJECT_BUILD_DIR)
?.let { File(it) }
?: fail<Nothing>("$ENV_VAR_PROJECT_BUILD_DIR environment variable not specified")
assertTrue(projectBuildDir.isDirectory) { "Project build dir does not exist: $projectBuildDir" }
return projectBuildDir.resolve("t").resolve("${testInfo.className}.${testInfo.methodName}").apply {
deleteRecursively()
mkdirs()
}
}
internal fun buildLibrary(sourceFile: File, libraryName: String, buildDir: File): File {
val configuration = CompilerConfiguration()
val environment = createForParallelTests(TestDisposable(), configuration, EnvironmentConfigFiles.JS_CONFIG_FILES)
configuration.put(CommonConfigurationKeys.MODULE_NAME, libraryName)
configuration.put(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
configuration.put(JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION, true)
configuration.put(JSConfigurationKeys.ZIP_FILE_SYSTEM_ACCESSOR, ZipFileSystemCacheableAccessor(2))
val psiManager = PsiManager.getInstance(environment.project)
val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem
val virtualFile = fileSystem.findFileByIoFile(sourceFile) ?: error("VirtualFile for $sourceFile not found")
val ktFiles = SingleRootFileViewProvider(psiManager, virtualFile).allFiles.filterIsInstance<KtFile>()
val diagnosticsReporter = DiagnosticReporterFactory.createPendingReporter()
val jsStdlib = File("libraries/stdlib/js-ir/build/classes/kotlin/js/main").absoluteFile
val dependencies = listOf(jsStdlib.absolutePath)
val moduleStructure = ModulesStructure(
project = environment.project,
mainModule = MainModule.SourceFiles(ktFiles),
compilerConfiguration = configuration,
dependencies = dependencies,
friendDependenciesPaths = emptyList()
)
val outputStream = ByteArrayOutputStream()
val messageCollector = PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.PLAIN_FULL_PATHS, true)
val analyzedOutput = compileModuleToAnalyzedFirWithPsi(
moduleStructure = moduleStructure,
ktFiles = ktFiles,
libraries = dependencies,
friendLibraries = emptyList(),
diagnosticsReporter = diagnosticsReporter,
incrementalDataProvider = null,
lookupTracker = null,
useWasmPlatform = false
)
val fir2IrActualizedResult = transformFirToIr(moduleStructure, analyzedOutput.output, diagnosticsReporter)
if (analyzedOutput.reportCompilationErrors(moduleStructure, diagnosticsReporter, messageCollector)) {
val messages = outputStream.toByteArray().toString(Charset.forName("UTF-8"))
fail<Nothing>("The following errors occurred compiling test:\n$messages")
}
val libraryDir = KFile(buildDir.resolve(libraryName).absolutePath)
val libraryFile = KFile(libraryDir.absolutePath + ".klib")
serializeFirKlib(
moduleStructure = moduleStructure,
firOutputs = analyzedOutput.output,
fir2IrActualizedResult = fir2IrActualizedResult,
outputKlibPath = libraryDir.absolutePath,
messageCollector = messageCollector,
diagnosticsReporter = diagnosticsReporter,
jsOutputName = libraryName,
useWasmPlatform = false
)
if (messageCollector.hasErrors()) {
val messages = outputStream.toByteArray().toString(Charset.forName("UTF-8"))
fail<Nothing>("The following errors occurred serializing test klib:\n$messages")
}
libraryDir.zipDirAs(libraryFile)
libraryDir.deleteRecursively()
return File(libraryFile.absolutePath)
}
@OptIn(ExperimentalLibraryAbiReader::class)
internal fun patchManifest(libraryFile: File, customManifest: LibraryManifest) {
val libraryKFile = KFile(libraryFile.absolutePath)
val manifestProperties = resolveSingleFileKlib(libraryKFile).manifestProperties
fun set(name: String, value: String?) {
assertTrue(!value.isNullOrBlank()) { "$name has invalid value: [$value]" }
manifestProperties[name] = value!!
}
set(KLIB_PROPERTY_BUILTINS_PLATFORM, customManifest.platform)
set(KLIB_PROPERTY_NATIVE_TARGETS, customManifest.nativeTargets.joinToString(" "))
set(KLIB_PROPERTY_COMPILER_VERSION, customManifest.compilerVersion)
set(KLIB_PROPERTY_ABI_VERSION, customManifest.abiVersion)
set(KLIB_PROPERTY_LIBRARY_VERSION, customManifest.libraryVersion)
set(KLIB_PROPERTY_IR_PROVIDER, customManifest.irProviderName)
assertEquals(libraryFile.extension, "klib")
val libraryDir = libraryFile.resolveSibling(libraryFile.nameWithoutExtension)
val libraryKDir = KFile(libraryDir.absolutePath)
libraryKFile.unzipTo(libraryKDir)
libraryKFile.deleteRecursively()
libraryDir.walkTopDown()
.filter { it.isFile && it.name == "manifest" }
.forEach { manifestProperties.saveToFile(KFile(it.absolutePath)) }
libraryKDir.zipDirAs(libraryKFile)
}
private const val ENV_VAR_PROJECT_BUILD_DIR = "PROJECT_BUILD_DIR"