From 4891a361838e10496ea6ad25ad2a9fa0bfb11ff2 Mon Sep 17 00:00:00 2001 From: Jens Geiregat Date: Tue, 27 Oct 2020 15:39:06 +0100 Subject: [PATCH] add Path.contains (#640) --- .../atrium/api/fluent/en_GB/pathAssertions.kt | 27 +++++ .../src/module/module-info.java | 2 +- .../api/fluent/en_GB/PathAssertionsSpec.kt | 6 +- .../api/infix/en_GB/PathAssertionsSpec.kt | 7 ++ .../fluent/en_GB/jdk8/PathAssertionsSpec.kt | 7 +- .../specs/integration/PathAssertionsSpec.kt | 101 ++++++++++++++++++ 6 files changed, 141 insertions(+), 9 deletions(-) diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/pathAssertions.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/pathAssertions.kt index cc5871aec..82364dd54 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/pathAssertions.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/pathAssertions.kt @@ -7,6 +7,7 @@ package ch.tutteli.atrium.api.fluent.en_GB import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.logic.* +import ch.tutteli.kbox.forElementAndForEachIn import java.nio.charset.Charset import java.nio.file.Path @@ -310,6 +311,32 @@ fun Expect.isAbsolute(): Expect = fun Expect.isRelative(): Expect = _logicAppend { isRelative() } +/** + * Expects that the subject of the assertion (a [Path]) is a directory; + * meaning that there is a file system entry at the location the [Path] points to and that is a directory. + * + * Every argument string is expected to exist as a child file or child directory for the subject of the assertion. + * + * This assertion _resolves_ symbolic links. + * Therefore, if a symbolic link exists at the location the subject points to, search will continue + * at the location the link points at. + * + * This assertion is not atomic with respect to concurrent file system operations on the paths the assertions works on. + * Its result, in particular its extended explanations, may be wrong if such concurrent file system operations + * take place. + * + * @return An [Expect] for the current subject of the assertion. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * + * @since 0.14.0 + */ +fun Expect.contains(path: String, vararg otherPaths: String): Expect = + isDirectory() and { + forElementAndForEachIn(path, otherPaths) { p -> + resolve(p) { exists() } + } + } + /** * Creates an [Expect] for the property [Path.extension][ch.tutteli.niok.extension] * (provided via [niok](https://github.com/robstoll/niok)) of the subject of the assertion, diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/module/module-info.java b/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/module/module-info.java index 6dbcac91a..a3c4c919a 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/module/module-info.java +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/module/module-info.java @@ -1,7 +1,7 @@ module ch.tutteli.atrium.api.fluent.en_GB { requires ch.tutteli.atrium.logic; requires kotlin.stdlib; - + requires ch.tutteli.kbox; exports ch.tutteli.atrium.api.fluent.en_GB; exports ch.tutteli.atrium.api.fluent.en_GB.creating.charsequence.contains.builders; diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/PathAssertionsSpec.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/PathAssertionsSpec.kt index f75225ae3..c5418a5a2 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/PathAssertionsSpec.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-jvm/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/PathAssertionsSpec.kt @@ -1,10 +1,7 @@ package ch.tutteli.atrium.api.fluent.en_GB import ch.tutteli.atrium.creating.Expect -import ch.tutteli.atrium.specs.fun0 -import ch.tutteli.atrium.specs.fun1 -import ch.tutteli.atrium.specs.fun3 -import ch.tutteli.atrium.specs.notImplemented +import ch.tutteli.atrium.specs.* import java.nio.file.Path import java.nio.file.Paths @@ -22,6 +19,7 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe fun0(Expect::isDirectory), fun0(Expect::isAbsolute), fun0(Expect::isRelative), + fun2>(Expect::contains), fun1(Expect::hasSameBinaryContentAs), fun3(Expect::hasSameTextualContentAs), fun1(Companion::hasSameTextualContentAsDefaultArgs) diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-jvm/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/PathAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-jvm/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/PathAssertionsSpec.kt index a5fa6a19b..2b6a0b6dd 100644 --- a/apis/infix-en_GB/atrium-api-infix-en_GB-jvm/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/PathAssertionsSpec.kt +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-jvm/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/PathAssertionsSpec.kt @@ -5,6 +5,7 @@ import ch.tutteli.atrium.specs.fun1 import ch.tutteli.atrium.specs.fun3 import ch.tutteli.atrium.specs.notImplemented import ch.tutteli.atrium.specs.testutils.WithAsciiReporter +import ch.tutteli.kbox.forElementAndForEachIn import java.nio.charset.Charset import java.nio.file.Path import java.nio.file.Paths @@ -23,6 +24,7 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe "toBe ${aDirectory::class.simpleName}" to Companion::isDirectory, "toBe ${relative::class.simpleName}" to Companion::isAbsolute, "toBe ${relative::class.simpleName}" to Companion::isRelative, + "contains not yet implemented in this API" to Companion::contains, fun1(Expect::hasSameBinaryContentAs), fun3(Companion::hasSameTextualContentAs), fun1(Companion::hasSameTextualContentAsDefaultArgs) @@ -38,6 +40,11 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe private fun isDirectory(expect: Expect) = expect toBe aDirectory private fun isAbsolute(expect: Expect) = expect toBe absolute private fun isRelative(expect: Expect) = expect toBe relative + private fun contains(expect: Expect, path: String, vararg otherPaths: String) = isDirectory(expect) and { + forElementAndForEachIn(path, otherPaths) { p -> + it resolve path(p) { it toBe existing } + } + } private fun hasSameTextualContentAs( expect: Expect, diff --git a/misc/deprecated/apis/fluent-en_GB-jdk8/atrium-api-fluent-en_GB-jdk8-jvm/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/jdk8/PathAssertionsSpec.kt b/misc/deprecated/apis/fluent-en_GB-jdk8/atrium-api-fluent-en_GB-jdk8-jvm/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/jdk8/PathAssertionsSpec.kt index 05dd777d6..123a0fbaf 100644 --- a/misc/deprecated/apis/fluent-en_GB-jdk8/atrium-api-fluent-en_GB-jdk8-jvm/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/jdk8/PathAssertionsSpec.kt +++ b/misc/deprecated/apis/fluent-en_GB-jdk8/atrium-api-fluent-en_GB-jdk8-jvm/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/jdk8/PathAssertionsSpec.kt @@ -3,14 +3,12 @@ package ch.tutteli.atrium.api.fluent.en_GB.jdk8 +import ch.tutteli.atrium.api.fluent.en_GB.contains import ch.tutteli.atrium.api.fluent.en_GB.isAbsolute import ch.tutteli.atrium.api.fluent.en_GB.isExecutable import ch.tutteli.atrium.api.fluent.en_GB.isRelative import ch.tutteli.atrium.creating.Expect -import ch.tutteli.atrium.specs.fun0 -import ch.tutteli.atrium.specs.fun1 -import ch.tutteli.atrium.specs.fun3 -import ch.tutteli.atrium.specs.notImplemented +import ch.tutteli.atrium.specs.* import java.nio.file.Path import java.nio.file.Paths @@ -28,6 +26,7 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe fun0(Expect::isDirectory), fun0(Expect::isAbsolute), // checks the new function from fluent-jvm because it is not implemented in fluent-jkd8 fun0(Expect::isRelative), // checks the new function from fluent-jvm because it is not implemented in fluent-jkd8 + fun2>(Expect::contains), // checks the new function from fluent-jvm because it is not implemented in fluent-jkd8 fun1(Expect::hasSameBinaryContentAs), fun3(Expect::hasSameTextualContentAs), fun1(Companion::hasSameTextualContentAsDefaultArgs) diff --git a/misc/specs/atrium-specs-jvm/src/main/kotlin/ch/tutteli/atrium/specs/integration/PathAssertionsSpec.kt b/misc/specs/atrium-specs-jvm/src/main/kotlin/ch/tutteli/atrium/specs/integration/PathAssertionsSpec.kt index 77cf46134..6703234c0 100644 --- a/misc/specs/atrium-specs-jvm/src/main/kotlin/ch/tutteli/atrium/specs/integration/PathAssertionsSpec.kt +++ b/misc/specs/atrium-specs-jvm/src/main/kotlin/ch/tutteli/atrium/specs/integration/PathAssertionsSpec.kt @@ -45,6 +45,7 @@ abstract class PathAssertionsSpec( isDirectory: Fun0, isAbsolute: Fun0, isRelative: Fun0, + contains: Fun2>, hasSameBinaryContentAs: Fun1, hasSameTextualContentAs: Fun3, hasSameTextualContentAsDefaultArgs: Fun1, @@ -66,6 +67,7 @@ abstract class PathAssertionsSpec( isDirectory.forSubjectLess(), isAbsolute.forSubjectLess(), isRelative.forSubjectLess(), + contains.forSubjectLess("a", arrayOf("b", "c")), hasSameBinaryContentAs.forSubjectLess(Paths.get("a")), hasSameTextualContentAs.forSubjectLess(Paths.get("a"), Charsets.ISO_8859_1, Charsets.ISO_8859_1), hasSameTextualContentAsDefaultArgs.forSubjectLess(Paths.get("a")) @@ -868,6 +870,103 @@ abstract class PathAssertionsSpec( } } + describeFun(contains) { + val containsFun = contains.lambda + + listOf( + ContainsTestData( "directory", "directories") { parent, name -> parent.newDirectory(name) }, + ContainsTestData("file", "files") { parent, name -> parent.newDirectory(name) } + ) .forEach {td -> + it("does not throw if the single parameter is a child ${td.singleName}") withAndWithoutSymlink { maybeLink -> + val folder = maybeLink.create(tempFolder.newDirectory("startDir")) + maybeLink.create(td.factory.invoke(folder, "a")) + expect(folder).containsFun("a", emptyArray()) + } + + it("does not throw if both parameters are child ${td.multipleName}") withAndWithoutSymlink { maybeLink -> + val folder = maybeLink.create(tempFolder.newDirectory("startDir")) + maybeLink.create(td.factory.invoke(folder, "a")) + maybeLink.create(td.factory.invoke(folder, "b")) + expect(folder).containsFun("a", arrayOf("b")) + } + + it("does not throw if three parameters are child ${td.multipleName}") withAndWithoutSymlink { maybeLink -> + val folder = maybeLink.create(tempFolder.newDirectory("startDir")) + maybeLink.create(td.factory.invoke(folder, "a")) + maybeLink.create(td.factory.invoke(folder, "b")) + maybeLink.create(td.factory.invoke(folder, "c")) + expect(folder).containsFun("a", arrayOf("b", "c")) + } + + it("it throws if the first ${td.singleName} does not exist") withAndWithoutSymlink { maybeLink -> + val folder = maybeLink.create(tempFolder.newDirectory("startDir")) + maybeLink.create(td.factory.invoke(folder, "file2")) + maybeLink.create(td.factory.invoke(folder, "file3")) + expect { + expect(folder).containsFun("file1", arrayOf("file2", "file3")) + }.toThrow().message { + contains("${TO.getDefault()}: ${EXIST.getDefault()}") + contains("file1") + containsNot("file2") + containsNot("file3") + } + } + + it("it throws if the second ${td.singleName} does not exist") withAndWithoutSymlink { maybeLink -> + val folder = maybeLink.create(tempFolder.newDirectory("startDir")) + maybeLink.create(td.factory.invoke(folder, "file1")) + maybeLink.create(td.factory.invoke(folder, "file3")) + expect { + expect(folder).containsFun("file1", arrayOf("file2", "file3")) + }.toThrow().message { + contains("${TO.getDefault()}: ${EXIST.getDefault()}") + contains("file2") + containsNot("file1") + containsNot("file3") + } + } + + it("it throws if third ${td.singleName} does not exist") withAndWithoutSymlink { maybeLink -> + val folder = maybeLink.create(tempFolder.newDirectory("startDir")) + maybeLink.create(td.factory.invoke(folder, "file1")) + maybeLink.create(td.factory.invoke(folder, "file2")) + expect { + expect(folder).containsFun("file1", arrayOf("file2", "file3")) + }.toThrow().message { + contains("${TO.getDefault()}: ${EXIST.getDefault()}") + containsNot("file2") + containsNot("file1") + contains("file3") + } + } + + it("it throws if the first and third ${td.singleName} do not exist") withAndWithoutSymlink { maybeLink -> + val folder = maybeLink.create(tempFolder.newDirectory("startDir")) + maybeLink.create(td.factory.invoke(folder, "file2")) + expect { + expect(folder).containsFun("file1", arrayOf("file2", "file3")) + }.toThrow().message { + contains("${TO.getDefault()}: ${EXIST.getDefault()}") + containsNot("file2") + contains("file1") + contains("file3") + } + } + + it("it throws if the directory does not exist") withAndWithoutSymlink { maybeLink -> + val folder = maybeLink.create(tempFolder.tmpDir.resolve("nonExistent")) + val expectedMessage = "$isDescr: ${A_DIRECTORY.getDefault()}" + + expect { + expect(folder).containsFun("file1", arrayOf("file2", "file3")) + }.toThrow().message { + contains(expectedMessage, FAILURE_DUE_TO_NO_SUCH_FILE.getDefault()) + containsExplanationFor(maybeLink) + } + } + } + } + describeFun(hasSameBinaryContentAs, hasSameTextualContentAs, hasSameTextualContentAsDefaultArgs) { val hasSameBinaryContentAsFun = hasSameBinaryContentAs.lambda val hasSameTextualContentAsFun = hasSameTextualContentAs.lambda @@ -1239,3 +1338,5 @@ private fun expectedPermissionTypeHintFor(type: Translatable, being: Translatabl type.getDefault(), being.getDefault() ) + +internal data class ContainsTestData(val singleName: String, val multipleName: String, val factory: (f: Path, name: String) -> Path)