| /* |
| * Copyright 2010-2018 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 test.io |
| |
| import java.io.File |
| import java.io.IOException |
| import java.util.* |
| import kotlin.test.* |
| |
| class FileTreeWalkTest { |
| |
| companion object { |
| val referenceFilenames = |
| listOf("1", "1/2", "1/3", "1/3/4.txt", "1/3/5.txt", "6", "7.txt", "8", "8/9.txt") |
| fun createTestFiles(): File { |
| val basedir = @Suppress("DEPRECATION") createTempDir() |
| for (name in referenceFilenames) { |
| val file = basedir.resolve(name) |
| if (file.extension.isEmpty()) |
| file.mkdir() |
| else |
| file.createNewFile() |
| } |
| return basedir |
| } |
| } |
| |
| @Test fun withSimple() { |
| val basedir = createTestFiles() |
| try { |
| val referenceNames = setOf("") + referenceFilenames |
| val namesTopDown = HashSet<String>() |
| for (file in basedir.walkTopDown()) { |
| val name = file.relativeToOrSelf(basedir).invariantSeparatorsPath |
| assertFalse(namesTopDown.contains(name), "$name is visited twice") |
| namesTopDown.add(name) |
| } |
| assertEquals(referenceNames, namesTopDown) |
| val namesBottomUp = HashSet<String>() |
| for (file in basedir.walkBottomUp()) { |
| val name = file.relativeToOrSelf(basedir).invariantSeparatorsPath |
| assertFalse(namesBottomUp.contains(name), "$name is visited twice") |
| namesBottomUp.add(name) |
| } |
| assertEquals(referenceNames, namesBottomUp) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun singleFile() { |
| val testFile = @Suppress("DEPRECATION") createTempFile() |
| val nonExistantFile = testFile.resolve("foo") |
| try { |
| for (walk in listOf(File::walkTopDown, File::walkBottomUp)) { |
| assertEquals(testFile, walk(testFile).single(), "${walk.name}") |
| assertEquals(testFile, testFile.walk().onEnter { false }.single(), "${walk.name} - enter should not be called for single file") |
| |
| assertTrue(walk(nonExistantFile).none(), "${walk.name} - enter should not be called for single file") |
| } |
| } |
| finally { |
| testFile.delete() |
| } |
| } |
| |
| @Test fun withEnterLeave() { |
| val basedir = createTestFiles() |
| try { |
| val referenceNames = |
| setOf("", "1", "1/2", "6", "8") |
| val namesTopDownEnter = HashSet<String>() |
| val namesTopDownLeave = HashSet<String>() |
| val namesTopDown = HashSet<String>() |
| fun enter(file: File): Boolean { |
| val name = file.relativeToOrSelf(basedir).invariantSeparatorsPath |
| assertTrue(file.isDirectory, "$name is not directory, only directories should be entered") |
| assertFalse(namesTopDownEnter.contains(name), "$name is entered twice") |
| assertFalse(namesTopDownLeave.contains(name), "$name is left before entrance") |
| if (file.name == "3") return false // filter out 3 |
| namesTopDownEnter.add(name) |
| return true |
| } |
| |
| fun leave(file: File) { |
| val name = file.relativeToOrSelf(basedir).invariantSeparatorsPath |
| assertTrue(file.isDirectory, "$name is not directory, only directories should be left") |
| assertFalse(namesTopDownLeave.contains(name), "$name is left twice") |
| namesTopDownLeave.add(name) |
| assertTrue(namesTopDownEnter.contains(name), "$name is left before entrance") |
| } |
| |
| fun visit(file: File) { |
| val name = file.relativeToOrSelf(basedir).invariantSeparatorsPath |
| if (file.isDirectory) { |
| assertTrue(namesTopDownEnter.contains(name), "$name is visited before entrance") |
| namesTopDown.add(name) |
| assertFalse(namesTopDownLeave.contains(name), "$name is visited after leaving") |
| } |
| if (file == basedir) |
| return |
| val parent = file.parentFile |
| if (parent != null) { |
| val parentName = parent.relativeToOrSelf(basedir).invariantSeparatorsPath |
| assertTrue(namesTopDownEnter.contains(parentName), |
| "$name is visited before entering its parent $parentName") |
| assertFalse(namesTopDownLeave.contains(parentName), |
| "$name is visited after leaving its parent $parentName") |
| } |
| } |
| for (file in basedir.walkTopDown().onEnter(::enter).onLeave(::leave)) { |
| visit(file) |
| } |
| assertEquals(referenceNames, namesTopDownEnter) |
| assertEquals(referenceNames, namesTopDownLeave) |
| namesTopDownEnter.clear() |
| namesTopDownLeave.clear() |
| namesTopDown.clear() |
| for (file in basedir.walkBottomUp().onEnter(::enter).onLeave(::leave)) { |
| visit(file) |
| } |
| assertEquals(referenceNames, namesTopDownEnter) |
| assertEquals(referenceNames, namesTopDownLeave) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun withFilterAndMap() { |
| val basedir = createTestFiles() |
| try { |
| val referenceNames = setOf("", "1", "1/2", "1/3", "6", "8") |
| assertEquals(referenceNames, basedir.walkTopDown().filter { it.isDirectory }.map { |
| it.relativeToOrSelf(basedir).invariantSeparatorsPath |
| }.toHashSet()) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| |
| } |
| |
| @Test fun withDeleteTxtTopDown() { |
| val basedir = createTestFiles() |
| try { |
| val referenceNames = setOf("", "1", "1/2", "1/3", "6", "8") |
| val namesTopDown = HashSet<String>() |
| fun enter(file: File) { |
| assertTrue(file.isDirectory) |
| for (child in file.listFiles()) { |
| if (child.name.endsWith("txt")) |
| child.delete() |
| } |
| } |
| for (file in basedir.walkTopDown().onEnter { enter(it); true }) { |
| val name = file.relativeToOrSelf(basedir).invariantSeparatorsPath |
| assertFalse(namesTopDown.contains(name), "$name is visited twice") |
| namesTopDown.add(name) |
| } |
| assertEquals(referenceNames, namesTopDown) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun withDeleteTxtBottomUp() { |
| val basedir = createTestFiles() |
| try { |
| val referenceNames = setOf("", "1", "1/2", "1/3", "6", "8") |
| val namesTopDown = HashSet<String>() |
| fun enter(file: File) { |
| assertTrue(file.isDirectory) |
| for (child in file.listFiles()) { |
| if (child.name.endsWith("txt")) |
| child.delete() |
| } |
| } |
| for (file in basedir.walkBottomUp().onEnter { enter(it); true }) { |
| val name = file.relativeToOrSelf(basedir).invariantSeparatorsPath |
| assertFalse(namesTopDown.contains(name), "$name is visited twice") |
| namesTopDown.add(name) |
| } |
| assertEquals(referenceNames, namesTopDown) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| private fun compareWalkResults(expected: Set<String>, basedir: File, filter: (File) -> Boolean) { |
| val namesTopDown = HashSet<String>() |
| for (file in basedir.walkTopDown().onEnter { filter(it) }) { |
| val name = file.toRelativeString(basedir) |
| assertFalse(namesTopDown.contains(name), "$name is visited twice") |
| namesTopDown.add(name) |
| } |
| assertEquals(expected, namesTopDown, "Top-down walk results differ") |
| val namesBottomUp = HashSet<String>() |
| for (file in basedir.walkBottomUp().onEnter { filter(it) }) { |
| val name = file.toRelativeString(basedir) |
| assertFalse(namesBottomUp.contains(name), "$name is visited twice") |
| namesBottomUp.add(name) |
| } |
| assertEquals(expected, namesBottomUp, "Bottom-up walk results differ") |
| } |
| |
| @Test fun withDirectoryFilter() { |
| val basedir = createTestFiles() |
| try { |
| // Every directory ended with 3 and its content is filtered out |
| fun filter(file: File): Boolean = !file.name.endsWith("3") |
| |
| val referenceNames = listOf("", "1", "1/2", "6", "7.txt", "8", "8/9.txt").map { File(it).path }.toSet() |
| compareWalkResults(referenceNames, basedir, ::filter) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun withTotalDirectoryFilter() { |
| val basedir = createTestFiles() |
| try { |
| val referenceNames = emptySet<String>() |
| compareWalkResults(referenceNames, basedir, { false }) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun withForEach() { |
| val basedir = createTestFiles() |
| try { |
| var i = 0 |
| basedir.walkTopDown().forEach { _ -> i++ } |
| assertEquals(10, i); |
| i = 0 |
| basedir.walkBottomUp().forEach { _ -> i++ } |
| assertEquals(10, i); |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun withCount() { |
| val basedir = createTestFiles() |
| try { |
| assertEquals(10, basedir.walkTopDown().count()); |
| assertEquals(10, basedir.walkBottomUp().count()); |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun withReduce() { |
| val basedir = createTestFiles() |
| try { |
| val res = basedir.walkTopDown().reduce { a, b -> if (a.canonicalPath > b.canonicalPath) a else b } |
| assertTrue(res.endsWith("9.txt"), "Expected end with 9.txt actual: ${res.name}") |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun withVisitorAndDepth() { |
| val basedir = createTestFiles() |
| try { |
| val files = HashSet<File>() |
| val dirs = HashSet<File>() |
| val failed = HashSet<String>() |
| val stack = ArrayList<File>() |
| fun beforeVisitDirectory(dir: File): Boolean { |
| stack.add(dir) |
| dirs.add(dir.relativeToOrSelf(basedir)) |
| return true |
| } |
| |
| fun afterVisitDirectory(dir: File) { |
| assertEquals(stack.last(), dir) |
| stack.removeAt(stack.lastIndex) |
| } |
| |
| fun visitFile(file: File) { |
| assertTrue(stack.last().listFiles().contains(file), file.toString()) |
| files.add(file.relativeToOrSelf(basedir)) |
| } |
| |
| fun visitDirectoryFailed(dir: File, @Suppress("UNUSED_PARAMETER") e: IOException) { |
| assertEquals(stack.last(), dir) |
| //stack.removeAt(stack.lastIndex) |
| failed.add(dir.name) |
| } |
| basedir.walkTopDown().onEnter(::beforeVisitDirectory).onLeave(::afterVisitDirectory). |
| onFail(::visitDirectoryFailed).forEach { it -> if (!it.isDirectory) visitFile(it) } |
| assertTrue(stack.isEmpty()) |
| for (fileName in arrayOf("", "1", "1/2", "1/3", "6", "8")) { |
| assertTrue(dirs.contains(File(fileName)), fileName) |
| } |
| for (fileName in arrayOf("1/3/4.txt", "1/3/4.txt", "7.txt", "8/9.txt")) { |
| assertTrue(files.contains(File(fileName)), fileName) |
| } |
| |
| //limit maxDepth |
| files.clear() |
| dirs.clear() |
| basedir.walkTopDown().onEnter(::beforeVisitDirectory).onLeave(::afterVisitDirectory).maxDepth(1). |
| forEach { it -> if (it != basedir) visitFile(it) } |
| assertTrue(stack.isEmpty()) |
| assertEquals(setOf(File("")), dirs) |
| for (file in arrayOf("1", "6", "7.txt", "8")) { |
| assertTrue(files.contains(File(file)), file.toString()) |
| } |
| |
| //restrict access |
| if (File(basedir, "1").setReadable(false)) { |
| try { |
| files.clear() |
| dirs.clear() |
| basedir.walkTopDown().onEnter(::beforeVisitDirectory).onLeave(::afterVisitDirectory). |
| onFail(::visitDirectoryFailed).forEach { it -> if (!it.isDirectory) visitFile(it) } |
| assertTrue(stack.isEmpty()) |
| assertEquals(setOf("1"), failed) |
| assertEquals(listOf("", "1", "6", "8").map { File(it) }.toSet(), dirs) |
| assertEquals(listOf("7.txt", "8/9.txt").map { File(it) }.toSet(), files) |
| } finally { |
| File(basedir, "1").setReadable(true) |
| } |
| } else { |
| System.err.println("cannot restrict access") |
| } |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun topDown() { |
| val basedir = createTestFiles() |
| try { |
| val visited = HashSet<File>() |
| val block: (File) -> Unit = { |
| assertTrue(!visited.contains(it), it.toString()) |
| assertTrue(it == basedir && visited.isEmpty() || visited.contains(it.parentFile), it.toString()) |
| visited.add(it) |
| } |
| basedir.walkTopDown().forEach(block) |
| assertEquals(10, visited.size) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun restrictedAccess() { |
| val basedir = createTestFiles() |
| val restricted = File(basedir, "1") |
| try { |
| if (restricted.setReadable(false)) { |
| val visited = HashSet<File>() |
| val block: (File) -> Unit = { |
| assertTrue(!visited.contains(it), it.toString()) |
| assertTrue(it == basedir && visited.isEmpty() || visited.contains(it.parentFile), it.toString()) |
| visited.add(it) |
| } |
| basedir.walkTopDown().forEach(block) |
| assertEquals(6, visited.size) |
| } |
| } finally { |
| restricted.setReadable(true) |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun backup() { |
| var count = 0 |
| fun makeBackup(file: File) { |
| count++ |
| val bakFile = File(file.toString() + ".bak") |
| file.copyTo(bakFile) |
| } |
| |
| val basedir1 = createTestFiles() |
| try { |
| basedir1.walkTopDown().forEach { |
| if (it.isFile) { |
| makeBackup(it) |
| } |
| } |
| assertEquals(4, count) |
| } finally { |
| basedir1.deleteRecursively() |
| } |
| |
| count = 0 |
| val basedir2 = createTestFiles() |
| try { |
| basedir2.walkTopDown().forEach { |
| if (it.isFile) { |
| makeBackup(it) |
| } |
| } |
| assertEquals(4, count) |
| } finally { |
| basedir2.deleteRecursively() |
| } |
| } |
| |
| @Test fun find() { |
| val basedir = createTestFiles() |
| try { |
| File(basedir, "8/4.txt").createNewFile() |
| var count = 0 |
| basedir.walkTopDown().takeWhile { _ -> count == 0 }.forEach { |
| if (it.name == "4.txt") { |
| count++ |
| } |
| } |
| assertEquals(1, count) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Test fun findGits() { |
| val basedir = createTestFiles() |
| try { |
| File(basedir, "1/3/.git").mkdir() |
| File(basedir, "1/2/.git").mkdir() |
| File(basedir, "6/.git").mkdir() |
| val found = HashSet<File>() |
| for (file in basedir.walkTopDown()) { |
| if (file.name == ".git") { |
| found.add(file.parentFile) |
| } |
| } |
| assertEquals(3, found.size) |
| } finally { |
| basedir.deleteRecursively() |
| } |
| } |
| |
| @Suppress("DEPRECATION") |
| @Test fun streamFileTree() { |
| val dir = createTempDir() |
| try { |
| val subDir1 = createTempDir(prefix = "d1_", directory = dir) |
| val subDir2 = createTempDir(prefix = "d2_", directory = dir) |
| createTempDir(prefix = "d1_", directory = subDir1) |
| createTempFile(prefix = "f1_", directory = subDir1) |
| createTempDir(prefix = "d1_", directory = subDir2) |
| assertEquals(6, dir.walkTopDown().count()) |
| } finally { |
| dir.deleteRecursively() |
| } |
| dir.mkdir() |
| try { |
| val it = dir.walkTopDown().iterator() |
| it.next() |
| assertFailsWith<NoSuchElementException>("Second call to next() should fail.") { it.next() } |
| } finally { |
| dir.delete() |
| } |
| } |
| |
| } |