Compare lookups after comparing build logs

This way tests are more informative when
compilation goes wrong.
This commit is contained in:
Alexey Tsvetkov
2017-06-23 17:10:59 +03:00
parent 82c977f2d2
commit 7e7fcd352c
2 changed files with 70 additions and 69 deletions
@@ -583,7 +583,15 @@ public class KotlinTestUtils {
assertEqualsToFile(expectedFile, actual, s -> s); assertEqualsToFile(expectedFile, actual, s -> s);
} }
public static void assertEqualsToFile(@NotNull String message, @NotNull File expectedFile, @NotNull String actual) {
assertEqualsToFile(message, expectedFile, actual, s -> s);
}
public static void assertEqualsToFile(@NotNull File expectedFile, @NotNull String actual, @NotNull Function1<String, String> sanitizer) { public static void assertEqualsToFile(@NotNull File expectedFile, @NotNull String actual, @NotNull Function1<String, String> sanitizer) {
assertEqualsToFile("Actual data differs from file content", expectedFile, actual, sanitizer);
}
public static void assertEqualsToFile(@NotNull String message, @NotNull File expectedFile, @NotNull String actual, @NotNull Function1<String, String> sanitizer) {
try { try {
String actualText = JetTestUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(actual.trim())); String actualText = JetTestUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(actual.trim()));
@@ -596,7 +604,7 @@ public class KotlinTestUtils {
String expectedText = JetTestUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(expected.trim())); String expectedText = JetTestUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(expected.trim()));
if (!Comparing.equal(sanitizer.invoke(expectedText), sanitizer.invoke(actualText))) { if (!Comparing.equal(sanitizer.invoke(expectedText), sanitizer.invoke(actualText))) {
throw new FileComparisonFailure("Actual data differs from file content: " + expectedFile.getName(), throw new FileComparisonFailure(message + ": " + expectedFile.getName(),
expected, actual, expectedFile.getAbsolutePath()); expected, actual, expectedFile.getAbsolutePath());
} }
} }
@@ -149,13 +149,6 @@ abstract class AbstractLookupTrackerTest : TestWithWorkingDir() {
// ignore KDoc like comments which starts with `/**`, example: /** text */ // ignore KDoc like comments which starts with `/**`, example: /** text */
private val COMMENT_WITH_LOOKUP_INFO = "/\\*[^*]+\\*/".toRegex() private val COMMENT_WITH_LOOKUP_INFO = "/\\*[^*]+\\*/".toRegex()
private fun removeCommentsCommentsWithLookupInfo(files: Iterable<File>) {
files.forEach {
val content = it.readText()
it.writeText(content.replace(COMMENT_WITH_LOOKUP_INFO, ""))
}
}
protected lateinit var srcDir: File protected lateinit var srcDir: File
protected lateinit var outDir: File protected lateinit var outDir: File
private var isICEnabledBackup: Boolean = false private var isICEnabledBackup: Boolean = false
@@ -185,11 +178,11 @@ abstract class AbstractLookupTrackerTest : TestWithWorkingDir() {
sb.appendln("Compiling files:") sb.appendln("Compiling files:")
for (compiledFile in compiledFiles.sortedBy { it.canonicalPath }) { for (compiledFile in compiledFiles.sortedBy { it.canonicalPath }) {
val lookupCount = lookupsCount[compiledFile] val lookupsFromFile = lookups[compiledFile]
val lookupStatus = when { val lookupStatus = when {
lookupCount == 0 -> "(no lookups)" lookupsFromFile == null -> "(unknown)"
lookupCount != null && lookupCount > 0 -> "" lookupsFromFile.isEmpty() -> "(no lookups)"
else -> "(unknown)" else -> ""
} }
sb.indentln("${compiledFile.toRelativeString(workingDir)}$lookupStatus") sb.indentln("${compiledFile.toRelativeString(workingDir)}$lookupStatus")
} }
@@ -207,30 +200,50 @@ abstract class AbstractLookupTrackerTest : TestWithWorkingDir() {
val steps = getModificationsToPerform(testDir, moduleNames = null, allowNoFilesWithSuffixInTestData = true, touchPolicy = TouchPolicy.CHECKSUM) val steps = getModificationsToPerform(testDir, moduleNames = null, allowNoFilesWithSuffixInTestData = true, touchPolicy = TouchPolicy.CHECKSUM)
.filter { it.isNotEmpty() } .filter { it.isNotEmpty() }
makeAndCheckLookups(dirtyFiles, workToOriginalFileMap, incrementalData).logOutput("INITIAL BUILD") val filesToLookups = arrayListOf<Map<File, List<LookupInfo>>>()
fun CompilerOutput.originalFilesToLookups() =
compiledFiles.associateBy({ workToOriginalFileMap[it]!! }, { lookups[it] ?: emptyList() })
make(dirtyFiles, incrementalData).apply {
logOutput("INITIAL BUILD")
filesToLookups.add(originalFilesToLookups())
}
for ((i, modifications) in steps.withIndex()) { for ((i, modifications) in steps.withIndex()) {
dirtyFiles = modifications.mapNotNullTo(HashSet()) { it.perform(workingDir, workToOriginalFileMap) } dirtyFiles = modifications.mapNotNullTo(HashSet()) { it.perform(workingDir, workToOriginalFileMap) }
makeAndCheckLookups(dirtyFiles, workToOriginalFileMap, incrementalData).logOutput("STEP ${i + 1}") make(dirtyFiles, incrementalData).apply {
logOutput("STEP ${i + 1}")
filesToLookups.add(originalFilesToLookups())
}
} }
val expectedBuildLog = File(testDir, "build.log") val expectedBuildLog = File(testDir, "build.log")
UsefulTestCase.assertSameLinesWithFile(expectedBuildLog.canonicalPath, sb.toString()) UsefulTestCase.assertSameLinesWithFile(expectedBuildLog.canonicalPath, sb.toString())
assertEquals(steps.size + 1, filesToLookups.size)
for ((i, lookupsAtStepI) in filesToLookups.withIndex()) {
val step = if (i == 0) "INITIAL BUILD" else "STEP $i"
for ((file, lookups) in lookupsAtStepI) {
checkLookupsInFile(step, file, lookups)
}
}
} }
private class CompilerOutput( private class CompilerOutput(
val exitCode: String, val exitCode: String,
val errors: List<String>, val errors: List<String>,
val compiledFiles: Iterable<File>, val compiledFiles: Iterable<File>,
val lookupsCount: Map<File, Int> val lookups: Map<File, List<LookupInfo>>
) )
private class IncrementalData(val sourceToOutput: MutableMap<File, MutableSet<File>> = hashMapOf()) private class IncrementalData(val sourceToOutput: MutableMap<File, MutableSet<File>> = hashMapOf())
private fun makeAndCheckLookups( private fun make(
filesToCompile: Iterable<File>, filesToCompile: Iterable<File>,
workingToOriginalFileMap: Map<File, File>,
incrementalData: IncrementalData incrementalData: IncrementalData
): CompilerOutput { ): CompilerOutput {
removeCommentsCommentsWithLookupInfo(filesToCompile) filesToCompile.forEach {
it.writeText(it.readText().replace(COMMENT_WITH_LOOKUP_INFO, ""))
}
for (dirtyFile in filesToCompile) { for (dirtyFile in filesToCompile) {
incrementalData.sourceToOutput.remove(dirtyFile)?.forEach { incrementalData.sourceToOutput.remove(dirtyFile)?.forEach {
@@ -259,74 +272,54 @@ abstract class AbstractLookupTrackerTest : TestWithWorkingDir() {
} }
} }
val lookupsCount = checkLookups(filesToCompile, lookupTracker, workingToOriginalFileMap) val lookups = lookupTracker.lookups.groupBy { File(it.filePath) }
return CompilerOutput(exitCode.toString(), messageCollector.errors, filesToCompile, lookupsCount) val lookupsFromCompiledFiles = filesToCompile.associate { it to (lookups[it] ?: emptyList()) }
return CompilerOutput(exitCode.toString(), messageCollector.errors, filesToCompile, lookupsFromCompiledFiles)
} }
protected open fun Services.Builder.registerAdditionalServices() {} protected open fun Services.Builder.registerAdditionalServices() {}
private fun checkLookups( private fun checkLookupsInFile(step: String, expectedFile: File, lookupsFromFile: List<LookupInfo>) {
compiledFiles: Iterable<File>, val text = expectedFile.readText().replace(COMMENT_WITH_LOOKUP_INFO, "")
lookupTracker: TestLookupTracker, val lines = text.lines().toMutableList()
workingToOriginalFileMap: Map<File, File>
): Map<File, Int> {
fun checkLookupsInFile(expectedFile: File, actualFile: File, lookupsFromFile: List<LookupInfo>) {
val text = actualFile.readText()
val matchResult = COMMENT_WITH_LOOKUP_INFO.find(text) for ((line, lookupsFromLine) in lookupsFromFile.groupBy { it.position.line }) {
if (matchResult != null) { val columnToLookups = lookupsFromLine.groupBy { it.position.column }.toList().sortedBy { it.first }
throw AssertionError("File $actualFile contains multiline comment in range ${matchResult.range}")
}
val lines = text.lines().toMutableList() val lineContent = lines[line - 1]
val parts = ArrayList<CharSequence>(columnToLookups.size * 2)
for ((line, lookupsFromLine) in lookupsFromFile.groupBy { it.position.line }) { var start = 0
val columnToLookups = lookupsFromLine.groupBy { it.position.column }.toList().sortedBy { it.first }
val lineContent = lines[line - 1] for ((column, lookupsFromColumn) in columnToLookups) {
val parts = ArrayList<CharSequence>(columnToLookups.size * 2) val end = column - 1
parts.add(lineContent.subSequence(start, end))
var start = 0 val lookups = lookupsFromColumn.distinct().joinToString(separator = " ", prefix = "/*", postfix = "*/") {
val rest = lineContent.substring(end)
for ((column, lookupsFromColumn) in columnToLookups) { val name =
val end = column - 1 when {
parts.add(lineContent.subSequence(start, end)) rest.startsWith(it.name) || // same name
rest.startsWith("$" + it.name) || // backing field
DECLARATION_STARTS_WITH.any { rest.startsWith(it) } // it's declaration
-> ""
else -> "(" + it.name + ")"
}
val lookups = lookupsFromColumn.distinct().joinToString(separator = " ", prefix = "/*", postfix = "*/") { it.scopeKind.toString()[0].toLowerCase().toString() + ":" + it.scopeFqName.let { if (it.isNotEmpty()) it else "<root>" } + name
val rest = lineContent.substring(end)
val name =
when {
rest.startsWith(it.name) || // same name
rest.startsWith("$" + it.name) || // backing field
DECLARATION_STARTS_WITH.any { rest.startsWith(it) } // it's declaration
-> ""
else -> "(" + it.name + ")"
}
it.scopeKind.toString()[0].toLowerCase().toString() + ":" + it.scopeFqName.let { if (it.isNotEmpty()) it else "<root>" } + name
}
parts.add(lookups)
start = end
} }
lines[line - 1] = parts.joinToString("") + lineContent.subSequence(start, lineContent.length) parts.add(lookups)
start = end
} }
val actual = lines.joinToString("\n") lines[line - 1] = parts.joinToString("") + lineContent.subSequence(start, lineContent.length)
KotlinTestUtils.assertEqualsToFile(expectedFile, actual)
} }
val fileToLookups = lookupTracker.lookups.groupBy { File(it.filePath) } val actual = lines.joinToString("\n")
return compiledFiles.associate { actualFile -> KotlinTestUtils.assertEqualsToFile("Lookups do not match after $step", expectedFile, actual)
val expectedFile = workingToOriginalFileMap[actualFile]!!
val lookupsInFile = fileToLookups[actualFile]
lookupsInFile?.let { checkLookupsInFile(expectedFile, actualFile, it) }
actualFile to (lookupsInFile?.size ?: 0)
}
} }
} }