Rewrite test of relocatable JPS IC caches

* Caches are compared as files now. It is a more robust approach
than comparing strings with dumped contents. E.g previous test
ignored differences in keys ordering, because dumping caches to string
was added for comparing caches after incremental and non-incremental builds,
which cannot be compared without sorting keys (see KT-32674).
* Calling setUp/tearDown twice within the same test instance was
relatively hacky and fragile. Also it complicated adding new test cases.
This commit is contained in:
Alexey Tsvetkov
2019-07-21 23:31:17 +03:00
parent 98aad3e00c
commit afa10cc125
5 changed files with 263 additions and 147 deletions
@@ -73,15 +73,6 @@ abstract class AbstractKotlinJpsBuildTestCase : BaseKotlinJpsBuildTestCase() {
companion object {
val TEST_DATA_PATH = "jps-plugin/testData/"
@Throws(IOException::class)
@JvmStatic
protected fun copyTestDataToTmpDir(testDataDir: File): File {
assert(testDataDir.exists()) { "Cannot find source folder " + testDataDir.absolutePath }
val tmpDir = FileUtil.createTempDirectory("jps-build", null)
FileUtil.copyDir(testDataDir, tmpDir)
return tmpDir
}
@JvmStatic
protected fun addKotlinStdlibDependency(modules: Collection<JpsModule>, exported: Boolean = false): JpsLibrary {
return addDependency(modules, KotlinJpsLibrary.JvmStdLib, exported)
@@ -20,7 +20,6 @@ import com.google.common.collect.Lists
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.testFramework.LightVirtualFile
@@ -60,9 +59,9 @@ import org.jetbrains.kotlin.codegen.JvmCodegenUtil
import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.config.KotlinCompilerVersion.TEST_IS_PRE_RELEASE_SYSTEM_PROPERTY
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.jps.incremental.CacheAttributesDiff
import org.jetbrains.kotlin.incremental.withIC
import org.jetbrains.kotlin.jps.build.KotlinJpsBuildTest.LibraryDependency.*
import org.jetbrains.kotlin.jps.build.KotlinJpsBuildTestBase.LibraryDependency.*
import org.jetbrains.kotlin.jps.incremental.CacheAttributesDiff
import org.jetbrains.kotlin.jps.model.kotlinCommonCompilerArguments
import org.jetbrains.kotlin.jps.model.kotlinCompilerArguments
import org.jetbrains.kotlin.jps.targets.KotlinModuleBuildTarget
@@ -82,15 +81,12 @@ import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.net.URLClassLoader
import java.nio.file.Paths
import java.util.*
import java.util.zip.ZipOutputStream
open class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() {
open class KotlinJpsBuildTest : KotlinJpsBuildTestBase() {
companion object {
private val PROJECT_NAME = "kotlinProject"
private val ADDITIONAL_MODULE_NAME = "module2"
private val JDK_NAME = "IDEA_JDK"
private val EXCLUDE_FILES = arrayOf("Excluded.class", "YetAnotherExcluded.class")
private val NOTHING = arrayOf<String>()
@@ -109,43 +105,6 @@ open class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() {
return result
}
@JvmStatic
protected fun assertFilesExistInOutput(module: JpsModule, vararg relativePaths: String) {
for (path in relativePaths) {
val outputFile = findFileInOutputDir(module, path)
assertTrue("Output not written: " + outputFile.absolutePath + "\n Directory contents: \n" + dirContents(outputFile.parentFile), outputFile.exists())
}
}
@JvmStatic
protected fun findFileInOutputDir(module: JpsModule, relativePath: String): File {
val outputUrl = JpsJavaExtensionService.getInstance().getOutputUrl(module, false)
assertNotNull(outputUrl)
val outputDir = File(JpsPathUtil.urlToPath(outputUrl))
return File(outputDir, relativePath)
}
@JvmStatic
protected fun assertFilesNotExistInOutput(module: JpsModule, vararg relativePaths: String) {
val outputUrl = JpsJavaExtensionService.getInstance().getOutputUrl(module, false)
assertNotNull(outputUrl)
val outputDir = File(JpsPathUtil.urlToPath(outputUrl))
for (path in relativePaths) {
val outputFile = File(outputDir, path)
assertFalse("Output directory \"" + outputFile.absolutePath + "\" contains \"" + path + "\"", outputFile.exists())
}
}
private fun dirContents(dir: File): String {
val files = dir.listFiles() ?: return "<not found>"
val builder = StringBuilder()
for (file in files) {
builder.append(" * ").append(file.name).append("\n")
}
return builder.toString()
}
@JvmStatic
protected fun klass(moduleName: String, classFqName: String): String {
val outputDirPrefix = "out/production/$moduleName/"
@@ -158,48 +117,6 @@ open class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() {
}
}
annotation class WorkingDir(val name: String)
enum class LibraryDependency {
NONE,
JVM_MOCK_RUNTIME,
JVM_FULL_RUNTIME,
JS_STDLIB,
}
protected lateinit var originalProjectDir: File
private val expectedOutputFile: File
get() = File(originalProjectDir, "expected-output.txt")
override fun setUp() {
super.setUp()
val currentTestMethod = this::class.members.firstOrNull { it.name == "test" + getTestName(false) }
val workingDirFromAnnotation = currentTestMethod?.annotations?.filterIsInstance<WorkingDir>()?.firstOrNull()?.name
val projDirPath = Paths.get(TEST_DATA_PATH, "general", workingDirFromAnnotation ?: getTestName(false))
originalProjectDir = projDirPath.toFile()
workDir = AbstractKotlinJpsBuildTestCase.copyTestDataToTmpDir(originalProjectDir)
orCreateProjectDir
}
override fun tearDown() {
FileUtil.delete(workDir)
super.tearDown()
}
override fun doGetProjectDir(): File = workDir
protected fun initProject(libraryDependency: LibraryDependency = NONE) {
addJdk(JDK_NAME)
loadProject(workDir.absolutePath + File.separator + PROJECT_NAME + ".ipr")
when (libraryDependency) {
NONE -> {}
JVM_MOCK_RUNTIME -> addKotlinMockRuntimeDependency()
JVM_FULL_RUNTIME -> addKotlinStdlibDependency()
JS_STDLIB -> addKotlinJavaScriptStdlibDependency()
}
}
fun doTest() {
initProject(JVM_MOCK_RUNTIME)
buildAllModules().assertSuccessful()
@@ -0,0 +1,111 @@
/*
* Copyright 2010-2019 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.jps.build
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.jps.model.java.JpsJavaExtensionService
import org.jetbrains.jps.model.module.JpsModule
import org.jetbrains.jps.util.JpsPathUtil
import java.io.File
import java.nio.file.Paths
open class KotlinJpsBuildTestBase : AbstractKotlinJpsBuildTestCase() {
protected lateinit var originalProjectDir: File
protected val expectedOutputFile: File
get() = File(originalProjectDir, "expected-output.txt")
override fun setUp() {
super.setUp()
val currentTestMethod = this::class.members.firstOrNull { it.name == "test" + getTestName(false) }
val workingDirFromAnnotation = currentTestMethod?.annotations?.filterIsInstance<WorkingDir>()?.firstOrNull()?.name
val projDirPath = Paths.get(
TEST_DATA_PATH,
"general",
workingDirFromAnnotation ?: getTestName(false)
)
originalProjectDir = projDirPath.toFile()
workDir = copyTestDataToTmpDir(originalProjectDir)
orCreateProjectDir
}
protected open fun copyTestDataToTmpDir(testDataDir: File): File {
assert(testDataDir.exists()) { "Cannot find source folder " + testDataDir.absolutePath }
val tmpDir = FileUtil.createTempDirectory("jps-build", null)
FileUtil.copyDir(testDataDir, tmpDir)
return tmpDir
}
override fun tearDown() {
workDir.deleteRecursively()
super.tearDown()
}
override fun doGetProjectDir(): File = workDir
annotation class WorkingDir(val name: String)
enum class LibraryDependency {
NONE,
JVM_MOCK_RUNTIME,
JVM_FULL_RUNTIME,
JS_STDLIB,
}
protected fun initProject(libraryDependency: LibraryDependency = LibraryDependency.NONE) {
addJdk(JDK_NAME)
loadProject(workDir.absolutePath + File.separator + PROJECT_NAME + ".ipr")
when (libraryDependency) {
LibraryDependency.NONE -> {}
LibraryDependency.JVM_MOCK_RUNTIME -> addKotlinMockRuntimeDependency()
LibraryDependency.JVM_FULL_RUNTIME -> addKotlinStdlibDependency()
LibraryDependency.JS_STDLIB -> addKotlinJavaScriptStdlibDependency()
}
}
companion object {
const val JDK_NAME = "IDEA_JDK"
const val PROJECT_NAME = "kotlinProject"
@JvmStatic
protected fun assertFilesExistInOutput(module: JpsModule, vararg relativePaths: String) {
for (path in relativePaths) {
val outputFile = findFileInOutputDir(module, path)
assertTrue("Output not written: " + outputFile.absolutePath + "\n Directory contents: \n" + dirContents(
outputFile.parentFile
), outputFile.exists())
}
}
@JvmStatic
protected fun findFileInOutputDir(module: JpsModule, relativePath: String): File {
val outputUrl = JpsJavaExtensionService.getInstance().getOutputUrl(module, false)
assertNotNull(outputUrl)
val outputDir = File(JpsPathUtil.urlToPath(outputUrl))
return File(outputDir, relativePath)
}
@JvmStatic
protected fun assertFilesNotExistInOutput(module: JpsModule, vararg relativePaths: String) {
val outputUrl = JpsJavaExtensionService.getInstance().getOutputUrl(module, false)
assertNotNull(outputUrl)
val outputDir = File(JpsPathUtil.urlToPath(outputUrl))
for (path in relativePaths) {
val outputFile = File(outputDir, path)
assertFalse("Output directory \"" + outputFile.absolutePath + "\" contains \"" + path + "\"", outputFile.exists())
}
}
private fun dirContents(dir: File): String {
val files = dir.listFiles() ?: return "<not found>"
val builder = StringBuilder()
for (file in files) {
builder.append(" * ").append(file.name).append("\n")
}
return builder.toString()
}
}
}
@@ -58,58 +58,6 @@ class KotlinJpsBuildTestIncremental : KotlinJpsBuildTest() {
checkOutputFilesList(File(workDir, "out/production"))
}
fun testRelocatableCaches() {
fun buildAndGetMappings(): String {
workDir.deleteRecursively()
workDir = AbstractKotlinJpsBuildTestCase.copyTestDataToTmpDir(originalProjectDir)
myDataStorageRoot.deleteRecursively()
myDataStorageRoot.mkdirs()
initProject(LibraryDependency.JVM_FULL_RUNTIME)
val workDirPath = FileUtil.toSystemIndependentName(workDir.absolutePath)
val logger = AbstractIncrementalJpsTest.MyLogger(workDirPath)
val projectDescriptor = createProjectDescriptor(BuildLoggingManager(logger))
val lookupTracker = TestLookupTracker()
val testingContext = TestingContext(lookupTracker, buildLogger = null)
myProject.setTestingContext(testingContext)
try {
doBuild(projectDescriptor, CompileScopeTestBuilder.rebuild().allModules()).assertSuccessful()
assertFilesExistInOutput(
myProject.modules.single(),
"MainKt.class", "Foo.class", "FooChild.class", "utils/Utils.class"
)
val kotlinContext = testingContext.kotlinCompileContext!!
val lookups = lookupTracker.lookups.mapTo(HashSet()) { LookupSymbol(it.name, it.scopeFqName) }
return createKotlinCachesDump(projectDescriptor, kotlinContext, lookups)
} finally {
projectDescriptor.release()
}
}
val mappings1 = buildAndGetMappings()
val projectDir1 = workDir
tearDown()
// hack to prevent setUp from creating the same dir after tearDown
projectDir1.mkdirs()
try {
setUp()
val projectDir2 = workDir
Assert.assertNotEquals(projectDir1, projectDir2)
val mappings2 = buildAndGetMappings()
Assert.assertEquals(mappings1, mappings2)
} finally {
projectDir1.deleteRecursively()
}
}
fun testJpsDaemonIC() {
fun testImpl() {
assertTrue("Daemon was not enabled!", isDaemonEnabled())
@@ -0,0 +1,149 @@
/*
* Copyright 2010-2019 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.jps.build
import org.jetbrains.jps.builders.*
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor
import org.jetbrains.jps.cmdline.ProjectDescriptor
import org.jetbrains.jps.incremental.ModuleBuildTarget
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
import org.jetbrains.kotlin.incremental.KOTLIN_CACHE_DIRECTORY_NAME
import org.jetbrains.kotlin.incremental.testingUtils.assertEqualDirectories
import org.jetbrains.kotlin.jps.build.fixtures.EnableICFixture
import org.jetbrains.kotlin.jps.incremental.KotlinDataContainerTarget
import java.io.File
import kotlin.reflect.KFunction1
class RelocatableJpsCachesTest : BaseKotlinJpsBuildTestCase() {
private val enableICFixture = EnableICFixture()
private lateinit var workingDir: File
override fun setUp() {
super.setUp()
enableICFixture.setUp()
workingDir = createTempDir("RelocatableJpsCachesTest", getTestName(false))
}
override fun tearDown() {
workingDir.deleteRecursively()
enableICFixture.tearDown()
super.tearDown()
}
fun testRelocatableCaches() {
buildTwiceAndCompare(RelocatableCacheTestCase::testRelocatableCaches)
}
private fun buildTwiceAndCompare(testMethod: KFunction1<RelocatableCacheTestCase, Unit>) {
val test1WorkingDir = workingDir.resolve("test1")
val test1KotlinCachesDir = workingDir.resolve("test1KotlinCaches")
val test2WorkingDir = workingDir.resolve("test2")
val test2KotlinCachesDir = workingDir.resolve("test2KotlinCaches")
runTestAndCopyKotlinCaches(test1WorkingDir, test1KotlinCachesDir, testMethod)
runTestAndCopyKotlinCaches(test2WorkingDir, test2KotlinCachesDir, testMethod)
assertEqualDirectories(test1KotlinCachesDir, test2KotlinCachesDir, forgiveExtraFiles = false)
}
private fun runTestAndCopyKotlinCaches(
projectWorkingDir: File,
dirToCopyKotlinCaches: File,
testMethod: KFunction1<RelocatableCacheTestCase, Unit>
) {
val testCase = object : RelocatableCacheTestCase(
projectWorkingDir = projectWorkingDir,
dirToCopyKotlinCaches = dirToCopyKotlinCaches
) {
override fun getName(): String = testMethod.name
}
testCase.exposedPrivateApi.setUp()
try {
testMethod.call(testCase)
} finally {
testCase.exposedPrivateApi.tearDown()
}
}
}
// the class should not be executed directly (hence it's abstract)
abstract class RelocatableCacheTestCase(
private val projectWorkingDir: File,
private val dirToCopyKotlinCaches: File
) : KotlinJpsBuildTestBase() {
val exposedPrivateApi = ExposedPrivateApi()
fun testRelocatableCaches() {
initProject(LibraryDependency.JVM_FULL_RUNTIME)
buildAllModules().assertSuccessful()
assertFilesExistInOutput(
myProject.modules.single(),
"MainKt.class", "Foo.class", "FooChild.class", "utils/Utils.class"
)
}
override fun copyTestDataToTmpDir(testDataDir: File): File {
testDataDir.copyRecursively(projectWorkingDir)
return projectWorkingDir
}
override fun doBuild(descriptor: ProjectDescriptor, scopeBuilder: CompileScopeTestBuilder?): BuildResult =
super.doBuild(descriptor, scopeBuilder).also {
copyKotlinCaches(descriptor)
}
private fun copyKotlinCaches(descriptor: ProjectDescriptor) {
val kotlinDataPaths = HashSet<File>()
val dataPaths = descriptor.dataManager.dataPaths
kotlinDataPaths.add(dataPaths.getTargetDataRoot(KotlinDataContainerTarget))
for (target in descriptor.buildTargetIndex.allTargets) {
if (!target.isKotlinTarget(descriptor)) continue
val targetDataRoot = descriptor.dataManager.dataPaths.getTargetDataRoot(target)
val kotlinDataRoot = targetDataRoot.resolve(KOTLIN_CACHE_DIRECTORY_NAME)
assert(kotlinDataRoot.isDirectory) { "Kotlin data root '$kotlinDataRoot' is not a directory" }
kotlinDataPaths.add(kotlinDataRoot)
}
dirToCopyKotlinCaches.deleteRecursively()
val originalStorageRoot = descriptor.dataManager.dataPaths.dataStorageRoot
for (kotlinCacheRoot in kotlinDataPaths) {
val relativePath = kotlinCacheRoot.relativeTo(originalStorageRoot).path
val targetDir = dirToCopyKotlinCaches.resolve(relativePath)
targetDir.parentFile.mkdirs()
kotlinCacheRoot.copyRecursively(targetDir)
}
}
private fun BuildTarget<*>.isKotlinTarget(descriptor: ProjectDescriptor): Boolean {
fun JavaSourceRootDescriptor.containsKotlinSources() = root.walk().any { it.isKotlinSourceFile }
if (this !is ModuleBuildTarget) return false
val rootDescriptors = computeRootDescriptors(
descriptor.model,
descriptor.moduleExcludeIndex,
descriptor.ignoredFileIndex,
descriptor.dataManager.dataPaths
)
return rootDescriptors.any { it is JavaSourceRootDescriptor && it.containsKotlinSources() }
}
// the famous Public Morozov pattern
inner class ExposedPrivateApi {
fun setUp() {
this@RelocatableCacheTestCase.setUp()
}
fun tearDown() {
this@RelocatableCacheTestCase.tearDown()
}
}
}