Extract incremental test modification utils

KT-8487
This commit is contained in:
Alexey Tsvetkov
2016-02-11 15:31:06 +03:00
parent 62dce975b3
commit f4aed97d52
5 changed files with 190 additions and 133 deletions
@@ -17,10 +17,12 @@
package org.jetbrains.kotlin.jps.build
import org.jetbrains.jps.incremental.ModuleBuildTarget
import org.jetbrains.kotlin.jps.build.incrementalModificationUtils.Modification
import org.jetbrains.kotlin.jps.build.incrementalModificationUtils.ModifyContent
import org.jetbrains.kotlin.jps.incremental.CacheVersionProvider
abstract class AbstractIncrementalCacheVersionChangedTest : AbstractIncrementalJpsTest(allowNoFilesWithSuffixInTestData = true) {
override fun performAdditionalModifications(modifications: List<AbstractIncrementalJpsTest.Modification>) {
override fun performAdditionalModifications(modifications: List<Modification>) {
val modifiedFiles = modifications.filterIsInstance<ModifyContent>().map { it.path }
val paths = projectDescriptor.dataManager.dataPaths
val targets = projectDescriptor.allModuleTargets
@@ -47,6 +47,10 @@ import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.incremental.LookupSymbol
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.jps.build.classFilesComparison.assertEqualDirectories
import org.jetbrains.kotlin.jps.build.incrementalModificationUtils.Modification
import org.jetbrains.kotlin.jps.build.incrementalModificationUtils.TouchPolicy
import org.jetbrains.kotlin.jps.build.incrementalModificationUtils.copyTestSources
import org.jetbrains.kotlin.jps.build.incrementalModificationUtils.getModificationsToPerform
import org.jetbrains.kotlin.jps.incremental.JpsLookupStorageProvider
import org.jetbrains.kotlin.jps.incremental.KotlinDataContainerTarget
import org.jetbrains.kotlin.jps.incremental.getKotlinCache
@@ -75,9 +79,6 @@ abstract class AbstractIncrementalJpsTest(
val DEBUG_LOGGING_ENABLED = System.getProperty("debug.logging.enabled") == "true"
private val COMMANDS = listOf("new", "touch", "delete")
private val COMMANDS_AS_REGEX_PART = COMMANDS.joinToString("|")
private val COMMANDS_AS_MESSAGE_PART = COMMANDS.joinToString("/") { "\".$it\"" }
private val BUILD_LOG_FILE_NAME = "build.log"
}
@@ -91,7 +92,7 @@ abstract class AbstractIncrementalJpsTest(
protected var lookupsDuringTest: MutableSet<LookupSymbol> by Delegates.notNull()
protected val mapWorkingToOriginalFile: MutableMap<File, File> = hashMapOf()
protected var mapWorkingToOriginalFile: MutableMap<File, File> = hashMapOf()
protected open val experimentalBuildLogFileName = "experimental-ic-build.log"
@@ -207,68 +208,6 @@ abstract class AbstractIncrementalJpsTest(
return build(CompileScopeTestBuilder.rebuild().allModules(), checkLookups = false)
}
private fun getModificationsToPerform(moduleNames: Collection<String>?): List<List<Modification>> {
fun getModificationsForIteration(newSuffix: String, touchSuffix: String, deleteSuffix: String): List<Modification> {
fun getDirPrefix(fileName: String): String {
val underscore = fileName.indexOf("_")
if (underscore != -1) {
val module = fileName.substring(0, underscore)
assert(moduleNames != null) { "File name has module prefix, but multi-module environment is absent" }
assert(module in moduleNames!!) { "Module not found for file with prefix: $fileName" }
return "$module/src"
}
assert(moduleNames == null) { "Test is multi-module, but file has no module prefix: $fileName" }
return "src"
}
val modifications = ArrayList<Modification>()
for (file in testDataDir.listFiles()!!) {
val fileName = file.name
if (fileName.endsWith(newSuffix)) {
modifications.add(ModifyContent(getDirPrefix(fileName) + "/" + fileName.removeSuffix(newSuffix), file))
}
if (fileName.endsWith(touchSuffix)) {
modifications.add(TouchFile(getDirPrefix(fileName) + "/" + fileName.removeSuffix(touchSuffix)))
}
if (fileName.endsWith(deleteSuffix)) {
modifications.add(DeleteFile(getDirPrefix(fileName) + "/" + fileName.removeSuffix(deleteSuffix)))
}
}
return modifications
}
val haveFilesWithoutNumbers = testDataDir.listFiles { it -> it.name.matches(".+\\.($COMMANDS_AS_REGEX_PART)$".toRegex()) }?.isNotEmpty() ?: false
val haveFilesWithNumbers = testDataDir.listFiles { it -> it.name.matches(".+\\.($COMMANDS_AS_REGEX_PART)\\.\\d+$".toRegex()) }?.isNotEmpty() ?: false
if (haveFilesWithoutNumbers && haveFilesWithNumbers) {
fail("Bad test data format: files ending with both unnumbered and numbered $COMMANDS_AS_MESSAGE_PART were found")
}
if (!haveFilesWithoutNumbers && !haveFilesWithNumbers) {
if (allowNoFilesWithSuffixInTestData) {
return listOf(listOf())
}
else {
fail("Bad test data format: no files ending with $COMMANDS_AS_MESSAGE_PART found")
}
}
if (haveFilesWithoutNumbers) {
return listOf(getModificationsForIteration(".new", ".touch", ".delete"))
}
else {
return (1..10)
.map { getModificationsForIteration(".new.$it", ".touch.$it", ".delete.$it") }
.filter { it.isNotEmpty() }
}
}
private fun rebuildAndCheckOutput(makeOverallResult: MakeResult) {
val outDir = File(getAbsolutePath("out"))
val outAfterMake = File(getAbsolutePath("out-after-make"))
@@ -430,8 +369,8 @@ abstract class AbstractIncrementalJpsTest(
private fun performModificationsAndMake(moduleNames: Set<String>?): List<MakeResult> {
val results = arrayListOf<MakeResult>()
val modifications = getModificationsToPerform(testDataDir, moduleNames, allowNoFilesWithSuffixInTestData, TouchPolicy.TIMESTAMP)
val modifications = getModificationsToPerform(moduleNames)
for (step in modifications) {
step.forEach { it.perform(workDir, mapWorkingToOriginalFile) }
performAdditionalModifications(step)
@@ -452,16 +391,13 @@ abstract class AbstractIncrementalJpsTest(
// null means one module
private fun configureModules(): Set<String>? {
fun prepareSources(relativePathToSrc: String, filePrefix: String) {
val srcDir = File(workDir, relativePathToSrc)
FileUtil.copyDir(testDataDir, srcDir) {
it.isDirectory || it.name.startsWith(filePrefix) && (it.name.endsWith(".kt") || it.name.endsWith(".java"))
}
srcDir.walk().forEach { mapWorkingToOriginalFile[it] = File(testDataDir, filePrefix + it.name) }
preProcessSources(srcDir)
fun prepareModuleSources(moduleName: String?) {
val sourceDirName = moduleName?.let { "$it/src" } ?: "src"
val filePrefix = moduleName?.let { "${it}_" } ?: ""
val sourceDestinationDir = File(workDir, sourceDirName)
val sourcesMapping = copyTestSources(testDataDir, sourceDestinationDir, filePrefix)
mapWorkingToOriginalFile.putAll(sourcesMapping)
preProcessSources(sourceDestinationDir)
}
var moduleNames: Set<String>?
@@ -469,11 +405,11 @@ abstract class AbstractIncrementalJpsTest(
val jdk = addJdk("my jdk")
val moduleDependencies = readModuleDependencies()
mapWorkingToOriginalFile = hashMapOf()
if (moduleDependencies == null) {
addModule("module", arrayOf(getAbsolutePath("src")), null, null, jdk)
prepareSources(relativePathToSrc = "src", filePrefix = "")
prepareModuleSources(moduleName = null)
moduleNames = null
}
else {
@@ -490,8 +426,7 @@ abstract class AbstractIncrementalJpsTest(
}
for (module in nameToModule.values) {
val moduleName = module.name
prepareSources(relativePathToSrc = "$moduleName/src", filePrefix = moduleName + "_")
prepareModuleSources(module.name)
}
moduleNames = nameToModule.keys
@@ -501,6 +436,7 @@ abstract class AbstractIncrementalJpsTest(
return moduleNames
}
protected open fun preProcessSources(srcDir: File) {
}
@@ -548,51 +484,6 @@ abstract class AbstractIncrementalJpsTest(
logBuf.append(KotlinTestUtils.replaceHashWithStar(message!!.replace("^$rootPath/".toRegex(), " "))).append('\n')
}
}
protected abstract class Modification(val path: String) {
abstract fun perform(workDir: File, mapping: MutableMap<File, File>)
override fun toString(): String = "${javaClass.simpleName} $path"
}
protected class ModifyContent(path: String, val dataFile: File) : Modification(path) {
override fun perform(workDir: File, mapping: MutableMap<File, File>) {
val file = File(workDir, path)
val oldLastModified = file.lastModified()
file.delete()
dataFile.copyTo(file)
val newLastModified = file.lastModified()
if (newLastModified <= oldLastModified) {
//Mac OS and some versions of Linux truncate timestamp to nearest second
file.setLastModified(oldLastModified + 1000)
}
mapping[file] = dataFile
}
}
protected class TouchFile(path: String) : Modification(path) {
override fun perform(workDir: File, mapping: MutableMap<File, File>) {
val file = File(workDir, path)
val oldLastModified = file.lastModified()
//Mac OS and some versions of Linux truncate timestamp to nearest second
file.setLastModified(Math.max(System.currentTimeMillis(), oldLastModified + 1000))
}
}
protected class DeleteFile(path: String) : Modification(path) {
override fun perform(workDir: File, mapping: MutableMap<File, File>) {
val fileToDelete = File(workDir, path)
if (!fileToDelete.delete()) {
throw AssertionError("Couldn't delete $fileToDelete")
}
mapping.remove(fileToDelete)
}
}
}
internal val ProjectDescriptor.allModuleTargets: Collection<ModuleBuildTarget>
@@ -21,9 +21,11 @@ import org.jetbrains.jps.builders.BuildTarget
import org.jetbrains.jps.builders.storage.BuildDataPaths
import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.incremental.CacheVersion
import org.jetbrains.kotlin.incremental.storage.BasicMapsOwner
import org.jetbrains.kotlin.jps.incremental.CacheVersionProvider
import org.jetbrains.kotlin.incremental.KOTLIN_CACHE_DIRECTORY_NAME
import org.jetbrains.kotlin.incremental.storage.BasicMapsOwner
import org.jetbrains.kotlin.jps.build.incrementalModificationUtils.Modification
import org.jetbrains.kotlin.jps.build.incrementalModificationUtils.ModifyContent
import org.jetbrains.kotlin.jps.incremental.CacheVersionProvider
import org.jetbrains.kotlin.jps.incremental.KotlinDataContainerTarget
import org.jetbrains.kotlin.utils.Printer
import java.io.File
@@ -40,11 +42,11 @@ abstract class AbstractIncrementalLazyCachesTest : AbstractIncrementalJpsTest()
UsefulTestCase.assertSameLinesWithFile(expectedFile.canonicalPath, actual)
}
override fun performAdditionalModifications(modifications: List<AbstractIncrementalJpsTest.Modification>) {
override fun performAdditionalModifications(modifications: List<Modification>) {
super.performAdditionalModifications(modifications)
for (modification in modifications) {
if (modification !is AbstractIncrementalJpsTest.ModifyContent) continue
if (modification !is ModifyContent) continue
val name = File(modification.path).name
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.jps.build
import com.intellij.openapi.util.SystemInfoRt
import org.jetbrains.jps.model.java.JavaSourceRootType
import org.jetbrains.kotlin.jps.build.incrementalModificationUtils.Modification
class IncrementalProjectPathCaseChangedTest : AbstractIncrementalJpsTest(checkDumpsCaseInsensitively = true) {
fun testProjectPathCaseChanged() {
@@ -36,7 +37,7 @@ class IncrementalProjectPathCaseChangedTest : AbstractIncrementalJpsTest(checkDu
super.doTest(testDataPath)
}
override fun performAdditionalModifications(modifications: List<AbstractIncrementalJpsTest.Modification>) {
override fun performAdditionalModifications(modifications: List<Modification>) {
val module = myProject.modules[0]
val sourceRoot = module.sourceRoots[0].url
assert(sourceRoot.endsWith("/src"))
@@ -0,0 +1,161 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.jps.build.incrementalModificationUtils
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.jps.builders.JpsBuildTestCase
import java.io.File
import java.util.*
private val COMMANDS = listOf("new", "touch", "delete")
private val COMMANDS_AS_REGEX_PART = COMMANDS.joinToString("|")
private val COMMANDS_AS_MESSAGE_PART = COMMANDS.joinToString("/") { "\".$it\"" }
enum class TouchPolicy {
TIMESTAMP,
CHECKSUM
}
fun copyTestSources(testDataDir: File, sourceDestinationDir: File, filePrefix: String): Map<File, File> {
val mapping = hashMapOf<File, File>()
FileUtil.copyDir(testDataDir, sourceDestinationDir) {
it.isDirectory || it.name.startsWith(filePrefix) && (it.name.endsWith(".kt") || it.name.endsWith(".java"))
}
sourceDestinationDir.walk().forEach { mapping[it] = File(testDataDir, filePrefix + it.name) }
return mapping
}
fun getModificationsToPerform(
testDataDir: File,
moduleNames: Collection<String>?,
allowNoFilesWithSuffixInTestData: Boolean,
touchPolicy: TouchPolicy
): List<List<Modification>> {
fun getModificationsForIteration(newSuffix: String, touchSuffix: String, deleteSuffix: String): List<Modification> {
fun getDirPrefix(fileName: String): String {
val underscore = fileName.indexOf("_")
if (underscore != -1) {
val module = fileName.substring(0, underscore)
assert(moduleNames != null) { "File name has module prefix, but multi-module environment is absent" }
assert(module in moduleNames!!) { "Module not found for file with prefix: $fileName" }
return "$module/src"
}
assert(moduleNames == null) { "Test is multi-module, but file has no module prefix: $fileName" }
return "src"
}
val modifications = ArrayList<Modification>()
for (file in testDataDir.listFiles()!!) {
val fileName = file.name
if (fileName.endsWith(newSuffix)) {
modifications.add(ModifyContent(getDirPrefix(fileName) + "/" + fileName.removeSuffix(newSuffix), file))
}
if (fileName.endsWith(touchSuffix)) {
modifications.add(TouchFile(getDirPrefix(fileName) + "/" + fileName.removeSuffix(touchSuffix), touchPolicy))
}
if (fileName.endsWith(deleteSuffix)) {
modifications.add(DeleteFile(getDirPrefix(fileName) + "/" + fileName.removeSuffix(deleteSuffix)))
}
}
return modifications
}
val haveFilesWithoutNumbers = testDataDir.listFiles { it -> it.name.matches(".+\\.($COMMANDS_AS_REGEX_PART)$".toRegex()) }?.isNotEmpty() ?: false
val haveFilesWithNumbers = testDataDir.listFiles { it -> it.name.matches(".+\\.($COMMANDS_AS_REGEX_PART)\\.\\d+$".toRegex()) }?.isNotEmpty() ?: false
if (haveFilesWithoutNumbers && haveFilesWithNumbers) {
JpsBuildTestCase.fail("Bad test data format: files ending with both unnumbered and numbered $COMMANDS_AS_MESSAGE_PART were found")
}
if (!haveFilesWithoutNumbers && !haveFilesWithNumbers) {
if (allowNoFilesWithSuffixInTestData) {
return listOf(listOf())
}
else {
JpsBuildTestCase.fail("Bad test data format: no files ending with $COMMANDS_AS_MESSAGE_PART found")
}
}
if (haveFilesWithoutNumbers) {
return listOf(getModificationsForIteration(".new", ".touch", ".delete"))
}
else {
return (1..10)
.map { getModificationsForIteration(".new.$it", ".touch.$it", ".delete.$it") }
.filter { it.isNotEmpty() }
}
}
abstract class Modification(val path: String) {
abstract fun perform(workDir: File, mapping: MutableMap<File, File>)
override fun toString(): String = "${javaClass.simpleName} $path"
}
class ModifyContent(path: String, val dataFile: File) : Modification(path) {
override fun perform(workDir: File, mapping: MutableMap<File, File>) {
val file = File(workDir, path)
val oldLastModified = file.lastModified()
file.delete()
dataFile.copyTo(file)
val newLastModified = file.lastModified()
if (newLastModified <= oldLastModified) {
//Mac OS and some versions of Linux truncate timestamp to nearest second
file.setLastModified(oldLastModified + 1000)
}
mapping[file] = dataFile
}
}
class TouchFile(path: String, private val touchPolicy: TouchPolicy) : Modification(path) {
override fun perform(workDir: File, mapping: MutableMap<File, File>) {
val file = File(workDir, path)
when (touchPolicy) {
TouchPolicy.TIMESTAMP -> {
val oldLastModified = file.lastModified()
//Mac OS and some versions of Linux truncate timestamp to nearest second
file.setLastModified(Math.max(System.currentTimeMillis(), oldLastModified + 1000))
}
TouchPolicy.CHECKSUM -> {
file.appendText(" ")
}
}
}
}
class DeleteFile(path: String) : Modification(path) {
override fun perform(workDir: File, mapping: MutableMap<File, File>) {
val fileToDelete = File(workDir, path)
if (!fileToDelete.delete()) {
throw AssertionError("Couldn't delete $fileToDelete")
}
mapping.remove(fileToDelete)
}
}