| /* |
| * Copyright 2010-2026 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 kotlin.jdk7.test |
| |
| import java.io.File |
| import java.net.URI |
| import java.nio.channels.SeekableByteChannel |
| import java.nio.file.* |
| import java.nio.file.attribute.BasicFileAttributes |
| import java.nio.file.attribute.FileAttribute |
| import java.nio.file.attribute.FileAttributeView |
| import java.nio.file.attribute.UserPrincipalLookupService |
| import java.nio.file.spi.FileSystemProvider |
| import kotlin.io.path.* |
| import kotlin.test.Test |
| import kotlin.test.assertContains |
| import kotlin.test.assertEquals |
| import kotlin.test.assertFailsWith |
| import kotlin.test.assertIs |
| import kotlin.test.assertTrue |
| |
| class PathRecursiveCopyBetweenIncompatibleFileSystemsTest : AbstractPathTest() { |
| @Test // Regression test for KT-85020 |
| fun copyBetweenFilesystemsWithIncompatiblePathSeparators() { |
| val root = createTempDirectory().cleanupRecursively() |
| |
| val srcDirOriginal = root.resolve("src").also { |
| it.resolve("a") |
| .resolve("b") |
| .resolve("c") |
| .resolve("test.txt") |
| .createParentDirectories() |
| .writeText("hello") |
| } |
| |
| val dst = root.resolve("dst").createDirectory() |
| |
| val mockFs = SharpFileSystemProvider().newTestFileSystem(srcDirOriginal.fileSystem) |
| val mockedSrc = mockFs.wrap(srcDirOriginal) |
| |
| mockedSrc.copyToRecursively(dst, followLinks = false, overwrite = false) |
| |
| val expectedPaths = srcDirOriginal.walk(PathWalkOption.INCLUDE_DIRECTORIES) |
| .map { it.relativeTo(srcDirOriginal).toString() } |
| .toSet() |
| val actualPaths = dst.walk(PathWalkOption.INCLUDE_DIRECTORIES) |
| .map { it.relativeTo(dst).toString() } |
| .toSet() |
| assertEquals(expectedPaths, actualPaths) |
| } |
| |
| @Test |
| fun copyFileWithNameCorrespondingToMultipleSegments() { |
| val root = createTempDirectory().cleanupRecursively() |
| |
| val src = root.resolve("src").also { |
| it.resolve("a") |
| .resolve("b#test.txt") |
| .createParentDirectories() |
| .writeText("hello") |
| } |
| |
| val dst = root.resolve("dst").createDirectory() |
| |
| val mockFs = SharpFileSystemProvider().newTestFileSystem(dst.fileSystem) |
| val mockedDst = mockFs.wrap(dst) |
| |
| var failed = false |
| src.copyToRecursively(mockedDst, followLinks = false, overwrite = false, onError = { srcPath, dstPath, exception -> |
| assertIs<IllegalFileNameException>(exception) |
| assertEquals( |
| src.fileSystem.getPath("a", "b#test.txt").toString(), |
| srcPath.relativeTo(src).toString() |
| ) |
| assertEquals("a#b#test.txt", dstPath.relativeTo(mockedDst).toString()) |
| failed = true |
| OnErrorResult.SKIP_SUBTREE |
| }) |
| assertTrue(failed, "Expected onError to be called, but it never happened") |
| } |
| } |
| |
| // FileSystemProvider, FileSystem and Path implementations below exists for testing purposes only, |
| // and solve the only one purpose - wrap an existing file system so that all |
| // its path will use "#" as a path separator. Thus, the name - SharpSomething. |
| private class SharpFileSystemProvider : FileSystemProvider() { |
| private var fileSystem: SharpFileSystem? = null |
| |
| fun newTestFileSystem(delegateFileSystem: FileSystem): SharpFileSystem { |
| check(fileSystem == null) { "Only one test filesystem can be registered in the provider" } |
| return SharpFileSystem(this, delegateFileSystem).also { fileSystem = it } |
| } |
| |
| override fun getScheme(): String = "sharp" |
| |
| override fun newFileSystem(uri: URI, env: MutableMap<String, *>): FileSystem = TODO() |
| |
| override fun getFileSystem(uri: URI): FileSystem = requireRegisteredFileSystem() |
| |
| override fun getPath(uri: URI): Path = requireRegisteredFileSystem().getPath(uri.path ?: "") |
| |
| override fun newByteChannel( |
| path: Path, |
| options: MutableSet<out OpenOption>, |
| vararg attrs: FileAttribute<*>, |
| ): SeekableByteChannel = delegateProvider().newByteChannel(path.toDelegatePath(), options, *attrs) |
| |
| override fun newDirectoryStream(dir: Path, filter: DirectoryStream.Filter<in Path>): DirectoryStream<Path> { |
| val testDir = dir as SharpPath |
| val stream = delegateProvider().newDirectoryStream(testDir.delegatePath) { delegatePath -> |
| filter.accept(testDir.fileSystem.wrap(delegatePath)) |
| } |
| return object : DirectoryStream<Path> { |
| override fun iterator(): MutableIterator<Path> = object : MutableIterator<Path> { |
| private val iterator = stream.iterator() |
| |
| override fun hasNext(): Boolean = iterator.hasNext() |
| |
| override fun next(): Path = testDir.fileSystem.wrap(iterator.next()) |
| |
| override fun remove() = iterator.remove() |
| } |
| |
| override fun close() = stream.close() |
| } |
| } |
| |
| override fun createDirectory(dir: Path, vararg attrs: FileAttribute<*>) { |
| delegateProvider().createDirectory(dir.toDelegatePath(), *attrs) |
| } |
| |
| override fun delete(path: Path) { |
| delegateProvider().delete(path.toDelegatePath()) |
| } |
| |
| override fun copy(source: Path, target: Path, vararg options: CopyOption) { |
| delegateProvider().copy(source.toDelegatePath(), target.toDelegatePath(), *options) |
| } |
| |
| override fun move(source: Path, target: Path, vararg options: CopyOption) { |
| delegateProvider().move(source.toDelegatePath(), target.toDelegatePath(), *options) |
| } |
| |
| override fun isSameFile(path: Path, path2: Path): Boolean { |
| if (path !is SharpPath || path2 !is SharpPath) return false |
| return delegateProvider().isSameFile(path.toDelegatePath(), path2.toDelegatePath()) |
| } |
| |
| override fun isHidden(path: Path): Boolean = delegateProvider().isHidden(path.toDelegatePath()) |
| |
| override fun getFileStore(path: Path): FileStore = delegateProvider().getFileStore(path.toDelegatePath()) |
| |
| override fun checkAccess(path: Path, vararg modes: AccessMode) { |
| delegateProvider().checkAccess(path.toDelegatePath(), *modes) |
| } |
| |
| override fun <V : FileAttributeView> getFileAttributeView(path: Path, type: Class<V>, vararg options: LinkOption): V? { |
| return delegateProvider().getFileAttributeView(path.toDelegatePath(), type, *options) |
| } |
| |
| override fun <A : BasicFileAttributes> readAttributes(path: Path, type: Class<A>, vararg options: LinkOption): A { |
| return delegateProvider().readAttributes(path.toDelegatePath(), type, *options) |
| } |
| |
| override fun readAttributes(path: Path, attributes: String, vararg options: LinkOption): MutableMap<String, Any> { |
| return delegateProvider().readAttributes(path.toDelegatePath(), attributes, *options) |
| } |
| |
| override fun setAttribute(path: Path, attribute: String, value: Any, vararg options: LinkOption) { |
| delegateProvider().setAttribute(path.toDelegatePath(), attribute, value, *options) |
| } |
| |
| private fun requireRegisteredFileSystem(): SharpFileSystem { |
| return fileSystem ?: throw IllegalStateException("No test filesystem is registered in the provider") |
| } |
| |
| private fun delegateProvider(): FileSystemProvider = requireRegisteredFileSystem().delegateFileSystem.provider() |
| } |
| |
| private class SharpFileSystem( |
| private val provider: SharpFileSystemProvider, |
| val delegateFileSystem: FileSystem, |
| ) : FileSystem() { |
| companion object { |
| const val PATH_SEPARATOR = "#" |
| } |
| |
| override fun provider(): FileSystemProvider = provider |
| |
| override fun close() = Unit |
| |
| override fun isOpen(): Boolean = delegateFileSystem.isOpen |
| |
| override fun isReadOnly(): Boolean = delegateFileSystem.isReadOnly |
| |
| override fun getSeparator(): String = PATH_SEPARATOR |
| |
| override fun getRootDirectories(): MutableIterable<Path> = |
| delegateFileSystem.rootDirectories.mapTo(ArrayList()) { wrap(it) } |
| |
| override fun getFileStores(): MutableIterable<FileStore> = delegateFileSystem.fileStores |
| |
| override fun supportedFileAttributeViews(): MutableSet<String> = |
| delegateFileSystem.supportedFileAttributeViews() |
| |
| override fun getPath(first: String, vararg more: String): Path { |
| val convertedFirst = first.fromTestSeparators() |
| val convertedMore = more.map { it.fromTestSeparators() }.toTypedArray() |
| return wrap(delegateFileSystem.getPath(convertedFirst, *convertedMore)) |
| } |
| |
| override fun getPathMatcher(syntaxAndPattern: String): PathMatcher = TODO() |
| |
| override fun getUserPrincipalLookupService(): UserPrincipalLookupService = |
| delegateFileSystem.userPrincipalLookupService |
| |
| override fun newWatchService(): WatchService = TODO() |
| fun wrap(delegatePath: Path): SharpPath = SharpPath(this, delegatePath) |
| |
| private fun String.fromTestSeparators(): String = |
| replace(PATH_SEPARATOR, delegateFileSystem.separator) |
| } |
| |
| private class SharpPath( |
| val fileSystem: SharpFileSystem, |
| val delegatePath: Path, |
| ) : Path { |
| override fun getFileSystem(): FileSystem = fileSystem |
| |
| override fun isAbsolute(): Boolean = delegatePath.isAbsolute |
| |
| override fun getRoot(): Path? = delegatePath.root?.let(fileSystem::wrap) |
| |
| override fun getFileName(): Path? = delegatePath.fileName?.let(fileSystem::wrap) |
| |
| override fun getParent(): Path? = delegatePath.parent?.let(fileSystem::wrap) |
| |
| override fun getNameCount(): Int = delegatePath.nameCount |
| |
| override fun getName(index: Int): Path = fileSystem.wrap(delegatePath.getName(index)) |
| |
| override fun subpath(beginIndex: Int, endIndex: Int): Path = fileSystem.wrap(delegatePath.subpath(beginIndex, endIndex)) |
| |
| override fun startsWith(other: Path): Boolean = other is SharpPath && delegatePath.startsWith(other.delegatePath) |
| |
| override fun startsWith(other: String): Boolean = startsWith(fileSystem.getPath(other)) |
| |
| override fun endsWith(other: Path): Boolean = other is SharpPath && delegatePath.endsWith(other.delegatePath) |
| |
| override fun endsWith(other: String): Boolean = endsWith(fileSystem.getPath(other)) |
| |
| override fun normalize(): Path = fileSystem.wrap(delegatePath.normalize()) |
| |
| override fun resolve(other: Path): Path { |
| other as SharpPath |
| return fileSystem.wrap(delegatePath.resolve(other.delegatePath)) |
| } |
| |
| override fun resolve(other: String): Path = resolve(fileSystem.getPath(other)) |
| |
| override fun resolveSibling(other: Path): Path { |
| other as SharpPath |
| return fileSystem.wrap(delegatePath.resolveSibling(other.delegatePath)) |
| } |
| |
| override fun resolveSibling(other: String): Path = resolveSibling(fileSystem.getPath(other)) |
| |
| override fun relativize(other: Path): Path = fileSystem.wrap(delegatePath.relativize((other as SharpPath).delegatePath)) |
| |
| override fun toUri(): URI = URI("${fileSystem.provider().scheme}:///${toString()}") |
| |
| override fun toAbsolutePath(): Path = fileSystem.wrap(delegatePath.toAbsolutePath()) |
| |
| override fun toRealPath(vararg options: LinkOption): Path = fileSystem.wrap(delegatePath.toRealPath(*options)) |
| |
| override fun toFile(): File = delegatePath.toFile() |
| |
| override fun register( |
| watcher: WatchService, |
| events: Array<out WatchEvent.Kind<*>>, |
| vararg modifiers: WatchEvent.Modifier, |
| ): WatchKey = TODO() |
| |
| override fun register(watcher: WatchService, vararg events: WatchEvent.Kind<*>): WatchKey = TODO() |
| |
| override fun iterator(): MutableIterator<Path> = |
| delegatePath.mapTo(ArrayList()) { fileSystem.wrap(it) }.iterator() |
| |
| override fun compareTo(other: Path): Int = delegatePath.compareTo((other as SharpPath).delegatePath) |
| |
| override fun equals(other: Any?): Boolean = |
| other is SharpPath && fileSystem == other.fileSystem && delegatePath == other.delegatePath |
| |
| override fun hashCode(): Int = 31 * fileSystem.hashCode() + delegatePath.hashCode() |
| |
| override fun toString(): String = |
| delegatePath.toString().replace(fileSystem.delegateFileSystem.separator, SharpFileSystem.PATH_SEPARATOR) |
| } |
| |
| private fun Path.toDelegatePath(): Path = (this as SharpPath).delegatePath |