From 2e228fdbfde09443f6ee40920592a377b5110345 Mon Sep 17 00:00:00 2001 From: Jakub Riegel Date: Thu, 15 Oct 2020 16:40:13 +0200 Subject: [PATCH] #630 add Path.isRelative method --- .../atrium/api/fluent/en_GB/pathAssertions.kt | 16 +++++++++++++ .../api/fluent/en_GB/PathAssertionsSpec.kt | 1 + .../atrium/api/infix/en_GB/keywords.kt | 8 +++++++ .../atrium/api/infix/en_GB/pathAssertions.kt | 17 +++++++++++++ .../api/infix/en_GB/PathAssertionsSpec.kt | 3 +++ .../kotlin/ch/tutteli/atrium/logic/path.kt | 1 + .../ch/tutteli/atrium/logic/PathAssertions.kt | 1 + .../logic/creating/filesystem/hints/hints.kt | 15 ++++++++++++ .../logic/impl/DefaultPathAssertions.kt | 8 ++++++- .../fluent/en_GB/jdk8/PathAssertionsSpec.kt | 2 ++ .../specs/integration/PathAssertionsSpec.kt | 24 +++++++++++++++++++ .../translations/DescriptionPathAssertion.kt | 4 +++- .../translations/DescriptionPathAssertion.kt | 2 ++ 13 files changed, 100 insertions(+), 2 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 45ac998950..1fcf8fb5b3 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 @@ -286,6 +286,22 @@ fun Expect.isRegularFile(): Expect = fun Expect.isDirectory(): Expect = _logicAppend { isDirectory() } +/** + * Expects that the subject of the assertion (a [Path]) is a relative path; + * meaning that the [Path] specified in this instance does not start at the file system root. + * + * 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. + * + * @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.isRelative(): Expect = + _logicAppend { isRelative() } + /** * 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/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 845c641257..dec3fb3c77 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 @@ -20,6 +20,7 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe fun0(Expect::isExecutable), fun0(Expect::isRegularFile), fun0(Expect::isDirectory), + fun0(Expect::isRelative), fun1(Expect::hasSameBinaryContentAs), fun3(Expect::hasSameTextualContentAs), fun1(Companion::hasSameTextualContentAsDefaultArgs) diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/keywords.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/keywords.kt index beac0bad05..42e36576c2 100644 --- a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/keywords.kt +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/keywords.kt @@ -22,6 +22,14 @@ object aRegularFile : Keyword */ object aDirectory : Keyword +/** + * A helper construct to allow expressing assertions about a path being relative. + * It can be used for a parameterless function so that it has one parameter and thus can be used as infix function. + * + * @since 0.14.0 + */ +object relative : Keyword + /** * Represents a helper construct which allows to express blankness. * It can be used for a parameterless function so that it has one parameter and thus can be used as infix function. diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/pathAssertions.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/pathAssertions.kt index 993c75a9ca..e5b017bdba 100644 --- a/apis/infix-en_GB/atrium-api-infix-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/pathAssertions.kt +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/pathAssertions.kt @@ -296,6 +296,23 @@ infix fun Expect.toBe(@Suppress("UNUSED_PARAMETER") aRegularFile: infix fun Expect.toBe(@Suppress("UNUSED_PARAMETER") aDirectory: aDirectory): Expect = _logicAppend { isDirectory() } +/** + * Expects that the subject of the assertion (a [Path]) is a relative path; + * meaning that the [Path] specified in this instance does not start at the file system root. + * + * 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. + * + * @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 + */ +infix fun Expect.toBe(@Suppress("UNUSED_PARAMETER") relative: relative): Expect = + _logicAppend { isRelative() } + + /** * 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/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 fdb1f810e7..0becea6b4a 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 @@ -21,6 +21,7 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe "toBe ${executable::class.simpleName}" to Companion::isExecutable, "toBe ${aRegularFile::class.simpleName}" to Companion::isRegularFile, "toBe ${aDirectory::class.simpleName}" to Companion::isDirectory, + "toBe ${relative::class.simpleName}" to Companion::isRelative, fun1(Expect::hasSameBinaryContentAs), fun3(Companion::hasSameTextualContentAs), fun1(Companion::hasSameTextualContentAsDefaultArgs) @@ -34,6 +35,7 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe private fun isExecutable(expect: Expect) = expect toBe executable private fun isRegularFile(expect: Expect) = expect toBe aRegularFile private fun isDirectory(expect: Expect) = expect toBe aDirectory + private fun isRelative(expect: Expect) = expect toBe relative private fun hasSameTextualContentAs( expect: Expect, @@ -62,6 +64,7 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe a1 toBe writable a1 toBe aRegularFile a1 toBe aDirectory + a1 toBe relative a1 hasSameTextualContentAs withEncoding(Paths.get("a")) a1 hasSameTextualContentAs Paths.get("a") } diff --git a/logic/atrium-logic-jvm/src/generated/kotlin/ch/tutteli/atrium/logic/path.kt b/logic/atrium-logic-jvm/src/generated/kotlin/ch/tutteli/atrium/logic/path.kt index f206d28c41..f7f2623bb7 100644 --- a/logic/atrium-logic-jvm/src/generated/kotlin/ch/tutteli/atrium/logic/path.kt +++ b/logic/atrium-logic-jvm/src/generated/kotlin/ch/tutteli/atrium/logic/path.kt @@ -32,6 +32,7 @@ fun AssertionContainer.isWritable(): Assertion = impl.isWritable(t fun AssertionContainer.isExecutable(): Assertion = impl.isExecutable(this) fun AssertionContainer.isRegularFile(): Assertion = impl.isRegularFile(this) fun AssertionContainer.isDirectory(): Assertion = impl.isDirectory(this) +fun AssertionContainer.isRelative(): Assertion = impl.isRelative(this) fun AssertionContainer.hasSameTextualContentAs(targetPath: Path, sourceCharset: Charset, targetCharset: Charset): Assertion = impl.hasSameTextualContentAs(this, targetPath, sourceCharset, targetCharset) diff --git a/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/PathAssertions.kt b/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/PathAssertions.kt index 2d22d1506d..39ca2ed22e 100644 --- a/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/PathAssertions.kt +++ b/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/PathAssertions.kt @@ -28,6 +28,7 @@ interface PathAssertions { fun isExecutable(container: AssertionContainer): Assertion fun isRegularFile(container: AssertionContainer): Assertion fun isDirectory(container: AssertionContainer): Assertion + fun isRelative(container: AssertionContainer): Assertion fun hasSameTextualContentAs( container: AssertionContainer, diff --git a/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/creating/filesystem/hints/hints.kt b/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/creating/filesystem/hints/hints.kt index 13b7b66055..562a2282a4 100644 --- a/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/creating/filesystem/hints/hints.kt +++ b/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/creating/filesystem/hints/hints.kt @@ -5,12 +5,15 @@ import ch.tutteli.atrium.assertions.AssertionGroup import ch.tutteli.atrium.assertions.builders.* import ch.tutteli.atrium.core.polyfills.fullName import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.creating.SubjectProvider +import ch.tutteli.atrium.domain.builders.creating.MapEntryAssertionsBuilder.value import ch.tutteli.atrium.logic.creating.transformers.impl.ThrowableThrownFailureHandler import ch.tutteli.atrium.logic.creating.filesystem.Failure import ch.tutteli.atrium.logic.creating.filesystem.IoResult import ch.tutteli.atrium.logic.creating.filesystem.Success import ch.tutteli.atrium.reporting.translating.Translatable import ch.tutteli.atrium.translations.DescriptionBasic +import ch.tutteli.atrium.translations.DescriptionPathAssertion import ch.tutteli.atrium.translations.DescriptionPathAssertion.* import ch.tutteli.niok.followSymbolicLink import ch.tutteli.niok.getFileAttributeView @@ -45,6 +48,18 @@ fun Descriptive.DescriptionOption.withFileAttributesFailu } } +fun Descriptive.DescriptionOption.withPathAttributesFailureHint( + provider: SubjectProvider +): Descriptive.DescriptionOption = + withFailureHintBasedOnDefinedSubject(provider) { path -> + explainForResolvedLink(path) { + describeWas(it.type) + } + } + +private val Path.type: DescriptionPathAssertion + get() = if (isAbsolute) ABSOLUTE_PATH else RELATIVE_PATH + /** * Internal for testing purposes only */ diff --git a/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/impl/DefaultPathAssertions.kt b/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/impl/DefaultPathAssertions.kt index 313c3378fd..bf64dd04c7 100644 --- a/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/impl/DefaultPathAssertions.kt +++ b/logic/atrium-logic-jvm/src/main/kotlin/ch/tutteli/atrium/logic/impl/DefaultPathAssertions.kt @@ -109,6 +109,13 @@ class DefaultPathAssertions : PathAssertions { override fun isDirectory(container: AssertionContainer): Assertion = fileTypeAssertion(container, A_DIRECTORY) { it.isDirectory } + override fun isRelative(container: AssertionContainer): Assertion = + assertionBuilder.descriptive + .withTest(container) { !it.isAbsolute } + .withPathAttributesFailureHint(container) + .withDescriptionAndRepresentation(DescriptionBasic.IS, RELATIVE_PATH) + .build() + private fun filePermissionAssertion( container: AssertionContainer, permissionName: Translatable, @@ -147,7 +154,6 @@ class DefaultPathAssertions : PathAssertions { .build() } - override fun fileName(container: AssertionContainer): FeatureExtractorBuilder.ExecutionStep = container.manualFeature(FILE_NAME) { fileName.toString() } 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 0d19b3915e..676278b91e 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 @@ -4,6 +4,7 @@ package ch.tutteli.atrium.api.fluent.en_GB.jdk8 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 @@ -24,6 +25,7 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe fun0(Expect::isExecutable), // checks the new function from fluent-jvm because it is not implemented in fluent-jkd8 fun0(Expect::isRegularFile), fun0(Expect::isDirectory), + fun0(Expect::isRelative), 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 59528edb41..493a429f07 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 @@ -19,6 +19,7 @@ import org.spekframework.spek2.dsl.Skip.No import org.spekframework.spek2.dsl.Skip.Yes import org.spekframework.spek2.dsl.TestBody import org.spekframework.spek2.style.specification.Suite +import java.io.File import java.io.IOException import java.nio.charset.Charset import java.nio.file.Files @@ -43,6 +44,7 @@ abstract class PathAssertionsSpec( isExecutable: Fun0, isRegularFile: Fun0, isDirectory: Fun0, + isRelative: Fun0, hasSameBinaryContentAs: Fun1, hasSameTextualContentAs: Fun3, hasSameTextualContentAsDefaultArgs: Fun1, @@ -62,6 +64,7 @@ abstract class PathAssertionsSpec( isExecutable.forSubjectLess(), isRegularFile.forSubjectLess(), isDirectory.forSubjectLess(), + isRelative.forSubjectLess(), hasSameBinaryContentAs.forSubjectLess(Paths.get("a")), hasSameTextualContentAs.forSubjectLess(Paths.get("a"), Charsets.ISO_8859_1, Charsets.ISO_8859_1), hasSameTextualContentAsDefaultArgs.forSubjectLess(Paths.get("a")) @@ -828,6 +831,27 @@ abstract class PathAssertionsSpec( } } + describeFun(isRelative) { + val isRelativeFun = isRelative.lambda + val expectedMessage = "$isDescr: ${RELATIVE_PATH.getDefault()}" + + it("throws an AssertionError for absolute path") { + val path = tempFolder.newFile("test") + + expect { + expect(path).isRelativeFun() + }.toThrow().message { + contains(expectedMessage, "${WAS.getDefault()}: ${ABSOLUTE_PATH.getDefault()}") + containsExplanationFor(maybeLink) + } + } + + it("does not throw for relative path") { + val path = Paths.get("test/bla.txt") + expect(path).isRelativeFun() + } + } + describeFun(hasSameBinaryContentAs, hasSameTextualContentAs, hasSameTextualContentAsDefaultArgs) { val hasSameBinaryContentAsFun = hasSameBinaryContentAs.lambda val hasSameTextualContentAsFun = hasSameTextualContentAs.lambda diff --git a/translations/de_CH/atrium-translations-de_CH-jvm/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionPathAssertion.kt b/translations/de_CH/atrium-translations-de_CH-jvm/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionPathAssertion.kt index fbb5af7f34..ce16fbe25e 100644 --- a/translations/de_CH/atrium-translations-de_CH-jvm/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionPathAssertion.kt +++ b/translations/de_CH/atrium-translations-de_CH-jvm/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionPathAssertion.kt @@ -13,6 +13,7 @@ import java.nio.file.Path * Contains the [DescriptiveAssertion.description]s of the assertion functions which are applicable to [Path]. */ enum class DescriptionPathAssertion(override val value: String) : StringBasedTranslatable { + ABSOLUTE_PATH("ein absoluter Pfad"), DOES_NOT_HAVE_PARENT("!! hat keinen Elternpfad"), ENDS_NOT_WITH("endet nicht mit"), ENDS_WITH("endet mit"), @@ -44,5 +45,6 @@ enum class DescriptionPathAssertion(override val value: String) : StringBasedTra HINT_CLOSEST_EXISTING_PARENT_DIRECTORY("das nächste, existierende Elternverzeichnis ist %s"), HINT_FOLLOWED_SYMBOLIC_LINK("folgte der symbolischen Verknüpfung %s nach %s"), HAS_SAME_TEXTUAL_CONTENT("hat denselben textlichen Inhalt mit Kodierung %s, %s"), - HAS_SAME_BINARY_CONTENT("hat denselben binären Inhalt") + HAS_SAME_BINARY_CONTENT("hat denselben binären Inhalt"), + RELATIVE_PATH("ein relativer Pfad") } diff --git a/translations/en_GB/atrium-translations-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionPathAssertion.kt b/translations/en_GB/atrium-translations-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionPathAssertion.kt index 3d8d56fdde..4ae9ed9a87 100644 --- a/translations/en_GB/atrium-translations-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionPathAssertion.kt +++ b/translations/en_GB/atrium-translations-en_GB-jvm/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionPathAssertion.kt @@ -8,6 +8,7 @@ package ch.tutteli.atrium.translations import ch.tutteli.atrium.reporting.translating.StringBasedTranslatable enum class DescriptionPathAssertion(override val value: String) : StringBasedTranslatable { + ABSOLUTE_PATH("an absolute path"), DOES_NOT_HAVE_PARENT("!! does not have a parent"), ENDS_NOT_WITH("does not end with"), ENDS_WITH("ends with"), @@ -40,4 +41,5 @@ enum class DescriptionPathAssertion(override val value: String) : StringBasedTra HINT_FOLLOWED_SYMBOLIC_LINK("followed the symbolic link %s to %s"), HAS_SAME_TEXTUAL_CONTENT("has same textual content with encoding %s, %s"), HAS_SAME_BINARY_CONTENT("has same binary content"), + RELATIVE_PATH("a relative path"), }