CLI: improve path relativization for compiler messages

On Windows, the absolute file for the root of the drive is `"C:\"`,
which differs from the absolute file for any other directory, which
doesn't end with `\`. This resulted in incorrect trimming of the first
character of the path name in `descendantRelativeTo`.

Also, do not use canonicalPath because there is no point in expanding
symbolic links here.

 #KT-40979 Fixed
This commit is contained in:
Alexander Udalov
2021-04-19 13:07:01 +02:00
committed by teamcityserver
parent f63dac26e6
commit c898805ac5
2 changed files with 81 additions and 11 deletions
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2021 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.integration
import com.intellij.openapi.util.SystemInfo
import org.jetbrains.kotlin.utils.fileUtils.descendantRelativeTo
import java.io.File
import kotlin.io.path.createSymbolicLinkPointingTo
class FilePathNormalizationTest : KotlinIntegrationTestBase() {
// This test checks that path normalization logic used in MessageRenderer.PLAIN_RELATIVE_PATHS works correctly.
// It compiles and runs a program in a separate process to be able to test how it works from different working directories.
// (It could be tested in the same process by changing the user.dir manually, but that could change behavior
// of other tests run in parallel.)
fun test() {
val descendantRelativeTo = File::descendantRelativeTo.name
val program = ProgramWithDependencyOnCompiler(
tmpdir, """
import org.jetbrains.kotlin.utils.fileUtils.$descendantRelativeTo
import java.io.File
fun main(args: Array<String>) {
println(File(args[0]).$descendantRelativeTo(File(".").absoluteFile).path)
}
""".trimIndent()
)
program.compile()
fun doTest(cwd: File, filePath: String, expectedWithForwardSlash: String) {
// We use "/" below for simplicity, but the actual paths in compiler messages use the system separator.
val expected = expectedWithForwardSlash.replace("/", File.separator)
val actual = program.run(cwd, filePath)
assertEquals("cwd: $cwd\nfilePath: $filePath\n", expected, actual)
}
doTest(tmpdir, "a", "a")
doTest(tmpdir, "./a", "a")
doTest(tmpdir, "a/../b", "b")
doTest(tmpdir, "..", "..")
doTest(tmpdir, "../a", "../a")
doTest(tmpdir, tmpdir.path + "/a", "a")
doTest(tmpdir, tmpdir.path + "/./a", "a")
val root = File("/")
doTest(root, "test", "test")
doTest(root, tmpdir.path + "/a", (tmpdir.path + "/a").removePrefix(root.absolutePath))
doTest(
root, "./test",
if (SystemInfo.isWindows) "./test" else "test"
)
// Check symbolic links, but skip file systems which don't support them (e.g. Windows).
fun doSymbolicLinkTest(cwd: File, source: File, target: File, expected: String) {
val link = try {
source.toPath().createSymbolicLinkPointingTo(target.toPath()).toFile()
} catch (e: Throwable) {
null
}
if (link != null) {
doTest(cwd, link.path, expected)
}
}
doSymbolicLinkTest(tmpdir, tmpdir / "a", tmpdir / "b", "a")
doSymbolicLinkTest(tmpdir / "unrelated", tmpdir / "a", tmpdir / "../b", tmpdir.path + "/a")
}
private operator fun File.div(x: String): File = File(this, x)
}
@@ -37,14 +37,9 @@ fun File.withReplacedExtensionOrNull(oldExt: String, newExt: String): File? {
* If this file does not belong to the [base] directory, it is returned unchanged.
*/
fun File.descendantRelativeTo(base: File): File {
val prefix = base.canonicalPath
val answer = this.canonicalPath
return if (answer.startsWith(prefix)) {
val prefixSize = prefix.length
if (answer.length > prefixSize) {
File(answer.substring(prefixSize + 1))
} else File("")
} else {
this
}
}
assert(base.isAbsolute) { "$base" }
assert(base.isDirectory) { "$base" }
val cwd = base.normalize()
val filePath = this.absoluteFile.normalize()
return if (filePath.startsWith(cwd)) filePath.relativeTo(cwd) else this
}