add Path.hasDirectoryEntry (#685)

This commit is contained in:
Joshua Gleitze
2020-10-30 09:13:06 +01:00
committed by GitHub
parent f7c0069f4e
commit 1d4c19c53e
40 changed files with 306 additions and 140 deletions

View File

@@ -7,7 +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 ch.tutteli.kbox.glue
import java.nio.charset.Charset
import java.nio.file.Path
@@ -196,7 +196,7 @@ fun <T : Path> Expect<T>.resolve(other: String, assertionCreator: Expect<Path>.(
* 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.
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*
@@ -255,7 +255,7 @@ fun <T : Path> Expect<T>.isExecutable(): Expect<T> =
* 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.
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*
@@ -275,7 +275,7 @@ fun <T : Path> Expect<T>.isRegularFile(): Expect<T> =
* 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.
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*
@@ -312,17 +312,17 @@ fun <T : Path> Expect<T>.isRelative(): Expect<T> =
_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.
* Expects that the subject of the assertion (a [Path]) is a directory having the provided entries.
* That means that there is a file system entry at the location the [Path] points to and that it is a directory.
* Furthermore, every argument string resolved against the subject yields an existing file system entry.
*
* 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 for the subject, but not for the entries.
* Therefore, if a symbolic link exists at the location the subject points to, the search will continue at the location
* the link points at. If a symbolic link exists at one of the entries, this will fulfill the respective assertion and
* the entrys symbolic link will not be followed.
*
* 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
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* The 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.
@@ -330,12 +330,8 @@ fun <T : Path> Expect<T>.isRelative(): Expect<T> =
*
* @since 0.14.0
*/
fun <T : Path> Expect<T>.contains(path: String, vararg otherPaths: String): Expect<T> =
isDirectory() and {
forElementAndForEachIn(path, otherPaths) { p ->
resolve(p) { exists() }
}
}
fun <T : Path> Expect<T>.hasDirectoryEntry(entry: String, vararg otherEntries: String): Expect<T> =
_logicAppend { hasDirectoryEntry(entry glue otherEntries) }
/**
* Creates an [Expect] for the property [Path.extension][ch.tutteli.niok.extension]

View File

@@ -19,15 +19,18 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe
fun0(Expect<Path>::isDirectory),
fun0(Expect<Path>::isAbsolute),
fun0(Expect<Path>::isRelative),
fun2<Path, String, Array<out String>>(Expect<Path>::contains),
Expect<Path>::hasDirectoryEntry.name to Companion::hasDirectoryEntrySingle,
fun2<Path, String, Array<out String>>(Expect<Path>::hasDirectoryEntry),
fun1(Expect<Path>::hasSameBinaryContentAs),
fun3(Expect<Path>::hasSameTextualContentAs),
fun1(Companion::hasSameTextualContentAsDefaultArgs)
Expect<Path>::hasSameTextualContentAs.name to Companion::hasSameTextualContentAsDefaultArgs
) {
companion object {
private fun hasSameTextualContentAsDefaultArgs(expect: Expect<Path>, targetPath: Path): Expect<Path> =
expect.hasSameTextualContentAs(targetPath)
private fun hasDirectoryEntrySingle(expect: Expect<Path>, entry: String) = expect.hasDirectoryEntry(entry)
}
@Suppress("unused", "UNUSED_VALUE")

View File

@@ -0,0 +1,13 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.path
import ch.tutteli.atrium.domain.builders.utils.VarArgHelper
/**
* Parameter object which collects directory entries (as [String]s).
* Use the function `directoryEntry(String, vararg String)` to create this representation.
*
* @since 0.14.0
*/
class DirectoryEntries(override val expected: String, override val otherExpected: Array<out String>) :
VarArgHelper<String>

View File

@@ -5,10 +5,12 @@
package ch.tutteli.atrium.api.infix.en_GB
import ch.tutteli.atrium.api.infix.en_GB.creating.path.DirectoryEntries
import ch.tutteli.atrium.api.infix.en_GB.creating.path.PathWithCreator
import ch.tutteli.atrium.api.infix.en_GB.creating.path.PathWithEncoding
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.logic.*
import ch.tutteli.kbox.glue
import java.nio.charset.Charset
import java.nio.file.Path
@@ -176,6 +178,60 @@ infix fun <T : Path> Expect<T>.parent(assertionCreator: Expect<Path>.() -> Unit)
infix fun <T : Path> Expect<T>.resolve(other: String): Expect<Path> =
_logic.resolve(other).transform()
/**
* Expects that the subject of the assertion (a [Path]) is a directory having the provided [entry].
* That means that there is a file system entry at the location the [Path] points to and that it is a directory.
* Furthermore, the argument string resolved against the subject yields an existing file system entry.
*
* This assertion _resolves_ symbolic links for the subject, but not for the [entry].
* Therefore, if a symbolic link exists at the location the subject points to, the search will continue at the location
* the link points at. If a symbolic link exists at the [entry], this will fulfill the assertion and the entrys
* symbolic link will not be followed.
*
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertions work on.
* The 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.
* @see [has]
*
* @since 0.14.0
*/
infix fun <T : Path> Expect<T>.hasDirectoryEntry(entry: String) =
_logicAppend { hasDirectoryEntry(listOf(entry)) }
/**
* Expects that the subject of the assertion (a [Path]) is a directory having the provided entries.
* That means that there is a file system entry at the location the [Path] points to and that it is a directory.
* Furthermore, every argument string resolved against the subject yields an existing file system entry.
*
* This assertion _resolves_ symbolic links for the subject, but not for the entries.
* Therefore, if a symbolic link exists at the location the subject points to, the search will continue at the location
* the link points at. If a symbolic link exists at one of the entries, this will fulfill the respective assertion and
* the entrys symbolic link will not be followed.
*
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertions work on.
* The 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.
* @see [directoryEntries]
* @see [hasDirectoryEntry]
*
* @since 0.14.0
*/
infix fun <T : Path> Expect<T>.has(directoryEntries: DirectoryEntries) =
_logicAppend { hasDirectoryEntry(directoryEntries.toList()) }
/**
* Helper function for [has] to create [DirectoryEntries] with the provided [entry] and the [otherEntries].
*
* @since 0.14.0
*/
fun directoryEntries(entry: String, vararg otherEntries: String) = DirectoryEntries(entry, otherEntries)
/**
* Expects that [PathWithCreator.path] resolves against this [Path], that the resolved [Path] holds all assertions the
* given [PathWithCreator.assertionCreator] creates for it and
@@ -206,7 +262,7 @@ fun <E> path(path: String, assertionCreator: Expect<E>.() -> Unit): PathWithCrea
* 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.
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*
@@ -264,7 +320,7 @@ infix fun <T : Path> Expect<T>.toBe(@Suppress("UNUSED_PARAMETER") executable: ex
* 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.
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*
@@ -284,7 +340,7 @@ infix fun <T : Path> Expect<T>.toBe(@Suppress("UNUSED_PARAMETER") aRegularFile:
* 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.
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion9 works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*

View File

@@ -1,6 +1,7 @@
module ch.tutteli.atrium.api.infix.en_GB {
requires ch.tutteli.atrium.logic;
requires kotlin.stdlib;
requires ch.tutteli.kbox;
requires java.base;
exports ch.tutteli.atrium.api.infix.en_GB;

View File

@@ -5,7 +5,6 @@ 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
@@ -24,7 +23,8 @@ 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<Path>::hasDirectoryEntry),
"has ${::directoryEntries.name}" to Companion::hasDirectoryEntryMultiple,
fun1(Expect<Path>::hasSameBinaryContentAs),
fun3(Companion::hasSameTextualContentAs),
fun1(Companion::hasSameTextualContentAsDefaultArgs)
@@ -40,11 +40,8 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe
private fun isDirectory(expect: Expect<Path>) = expect toBe aDirectory
private fun isAbsolute(expect: Expect<Path>) = expect toBe absolute
private fun isRelative(expect: Expect<Path>) = expect toBe relative
private fun contains(expect: Expect<Path>, path: String, vararg otherPaths: String) = isDirectory(expect) and {
forElementAndForEachIn(path, otherPaths) { p ->
it resolve path(p) { it toBe existing }
}
}
private fun hasDirectoryEntryMultiple(expect: Expect<Path>, entry: String, vararg otherEntries: String) =
expect has directoryEntries(entry, *otherEntries)
private fun hasSameTextualContentAs(
expect: Expect<Path>,
@@ -77,6 +74,9 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe
a1 toBe relative
a1 hasSameTextualContentAs withEncoding(Paths.get("a"))
a1 hasSameTextualContentAs Paths.get("a")
a1 resolve "a"
a1 hasDirectoryEntry "a"
a1 has directoryEntries("a", "b", "c")
}
}

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle
@@ -15,6 +16,7 @@ import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.creating.AssertionContainer
import ch.tutteli.atrium.logic.creating.transformers.FeatureExtractorBuilder
import java.nio.charset.Charset
import java.nio.file.LinkOption
import java.nio.file.Path
import ch.tutteli.atrium.core.ExperimentalNewExpectTypes
import ch.tutteli.atrium.logic.impl.DefaultPathAssertions
@@ -24,8 +26,8 @@ fun <T : Path> AssertionContainer<T>.startsNotWith(expected: Path): Assertion =
fun <T : Path> AssertionContainer<T>.endsWith(expected: Path): Assertion = impl.endsWith(this, expected)
fun <T : Path> AssertionContainer<T>.endsNotWith(expected: Path): Assertion = impl.endsNotWith(this, expected)
fun <T : Path> AssertionContainer<T>.exists(): Assertion = impl.exists(this)
fun <T : Path> AssertionContainer<T>.existsNot(): Assertion = impl.existsNot(this)
fun <T : Path> AssertionContainer<T>.exists(linkOption: LinkOption? = null): Assertion = impl.exists(this, linkOption)
fun <T : Path> AssertionContainer<T>.existsNot(linkOption: LinkOption? = null): Assertion = impl.existsNot(this, linkOption)
fun <T : Path> AssertionContainer<T>.isReadable(): Assertion = impl.isReadable(this)
fun <T : Path> AssertionContainer<T>.isWritable(): Assertion = impl.isWritable(this)
@@ -46,6 +48,8 @@ fun <T : Path> AssertionContainer<T>.fileNameWithoutExtension(): FeatureExtracto
fun <T : Path> AssertionContainer<T>.parent(): FeatureExtractorBuilder.ExecutionStep<T, Path> = impl.parent(this)
fun <T : Path> AssertionContainer<T>.resolve(other: String): FeatureExtractorBuilder.ExecutionStep<T, Path> = impl.resolve(this, other)
fun <T : Path> AssertionContainer<T>.hasDirectoryEntry(entries: List<String>): Assertion = impl.hasDirectoryEntry(this, entries)
@Suppress("DEPRECATION" /* OptIn is only available since 1.3.70 which we cannot use if we want to support 1.2 */)
@UseExperimental(ExperimentalNewExpectTypes::class)
private inline val <T> AssertionContainer<T>.impl: PathAssertions

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -9,6 +9,7 @@ import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.creating.AssertionContainer
import ch.tutteli.atrium.logic.creating.transformers.FeatureExtractorBuilder
import java.nio.charset.Charset
import java.nio.file.LinkOption
import java.nio.file.Path
/**
@@ -20,8 +21,8 @@ interface PathAssertions {
fun <T : Path> endsWith(container: AssertionContainer<T>, expected: Path): Assertion
fun <T : Path> endsNotWith(container: AssertionContainer<T>, expected: Path): Assertion
fun <T : Path> exists(container: AssertionContainer<T>): Assertion
fun <T : Path> existsNot(container: AssertionContainer<T>): Assertion
fun <T : Path> exists(container: AssertionContainer<T>, linkOption: LinkOption? = null): Assertion
fun <T : Path> existsNot(container: AssertionContainer<T>, linkOption: LinkOption? = null): Assertion
fun <T : Path> isReadable(container: AssertionContainer<T>): Assertion
fun <T : Path> isWritable(container: AssertionContainer<T>): Assertion
@@ -44,5 +45,10 @@ interface PathAssertions {
fun <T : Path> extension(container: AssertionContainer<T>): FeatureExtractorBuilder.ExecutionStep<T, String>
fun <T : Path> fileNameWithoutExtension(container: AssertionContainer<T>): FeatureExtractorBuilder.ExecutionStep<T, String>
fun <T : Path> parent(container: AssertionContainer<T>): FeatureExtractorBuilder.ExecutionStep<T, Path>
fun <T : Path> resolve(container: AssertionContainer<T>, other: String): FeatureExtractorBuilder.ExecutionStep<T, Path>
fun <T : Path> resolve(
container: AssertionContainer<T>,
other: String
): FeatureExtractorBuilder.ExecutionStep<T, Path>
fun <T : Path> hasDirectoryEntry(container: AssertionContainer<T>, entries: List<String>): Assertion
}

View File

@@ -7,6 +7,7 @@ package ch.tutteli.atrium.logic.impl
import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.assertions.builders.assertionBuilder
import ch.tutteli.atrium.assertions.builders.invisibleGroup
import ch.tutteli.atrium.core.None
import ch.tutteli.atrium.core.Some
import ch.tutteli.atrium.creating.AssertionContainer
@@ -24,10 +25,8 @@ import ch.tutteli.atrium.translations.DescriptionBasic
import ch.tutteli.atrium.translations.DescriptionPathAssertion.*
import ch.tutteli.niok.*
import java.nio.charset.Charset
import java.nio.file.AccessDeniedException
import java.nio.file.AccessMode
import java.nio.file.NoSuchFileException
import java.nio.file.Path
import java.nio.file.*
import java.nio.file.LinkOption.NOFOLLOW_LINKS
import java.nio.file.attribute.BasicFileAttributes
class DefaultPathAssertions : PathAssertions {
@@ -60,8 +59,8 @@ class DefaultPathAssertions : PathAssertions {
it.readAllBytes().contentEquals(targetPath.readAllBytes())
}
override fun <T : Path> exists(container: AssertionContainer<T>): Assertion =
changeSubjectToFileAttributes(container) { fileAttributesExpect ->
override fun <T : Path> exists(container: AssertionContainer<T>, linkOption: LinkOption?): Assertion =
changeSubjectToFileAttributes(container, linkOption) { fileAttributesExpect ->
assertionBuilder.descriptive
.withTest(fileAttributesExpect) { it is Success }
.withIOExceptionFailureHint(fileAttributesExpect) { realPath, exception ->
@@ -78,8 +77,9 @@ class DefaultPathAssertions : PathAssertions {
.build()
}
override fun <T : Path> existsNot(container: AssertionContainer<T>): Assertion =
changeSubjectToFileAttributes(container) { fileAttributesExpect ->
override fun <T : Path> existsNot(container: AssertionContainer<T>, linkOption: LinkOption?): Assertion =
changeSubjectToFileAttributes(container, linkOption) { fileAttributesExpect ->
assertionBuilder.descriptive
.withTest(fileAttributesExpect) { it is Failure && it.exception is NoSuchFileException }
.withFileAttributesFailureHint(fileAttributesExpect)
@@ -89,9 +89,12 @@ class DefaultPathAssertions : PathAssertions {
private inline fun <T : Path, R> changeSubjectToFileAttributes(
container: AssertionContainer<T>,
linkOption: LinkOption? = null,
block: (Expect<IoResult<BasicFileAttributes>>) -> R
): R = container.changeSubject.unreported {
it.runCatchingIo { readAttributes<BasicFileAttributes>() }
it.runCatchingIo<BasicFileAttributes> {
if (linkOption == null) readAttributes() else readAttributes(linkOption)
}
}.let(block)
override fun <T : Path> isReadable(container: AssertionContainer<T>): Assertion =
@@ -180,4 +183,11 @@ class DefaultPathAssertions : PathAssertions {
other: String
): FeatureExtractorBuilder.ExecutionStep<T, Path> = container.f1<T, String, Path>(Path::resolve, other)
override fun <T : Path> hasDirectoryEntry(container: AssertionContainer<T>, entries: List<String>): Assertion =
assertionBuilder.invisibleGroup.withAssertions(
listOf(container.isDirectory()) +
entries.map { entry ->
container.resolve(entry).collect { _logicAppend { exists(NOFOLLOW_LINKS) } }
}
).build()
}

View File

@@ -1,3 +1,4 @@
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -54,7 +54,7 @@ def createGenerateLogicTaskForPackage(
String generatedFolder = project.generatedFolder
return task("generateLogic_${relativePackagePath.replaceAll('/', '_')}", description: "generates ext. methods for pacakge $relativePackagePath") {
return task("generateLogic_${relativePackagePath.replaceAll('/', '_')}", description: "generates ext. methods for package $relativePackagePath") {
def packagePath = 'ch/tutteli/atrium/logic' + relativePackagePath + (suffix != '' ? "/" + suffix : '')
def fullPackage = packagePath.replaceAll('/', '.')
def path = "$project.projectDir/src/main/kotlin/$packagePath/"
@@ -73,6 +73,7 @@ def createGenerateLogicTaskForPackage(
//TODO delete all files in folder first (as we might have removed things)
def header = """\
// @formatter:off
//---------------------------------------------------
// Generated content, modify:
// logic/generateLogic.gradle

View File

@@ -254,7 +254,7 @@ fun <T : Path> Expect<T>.resolve(other: String, assertionCreator: Expect<Path>.(
* 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.
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*
@@ -297,7 +297,7 @@ fun <T : Path> Expect<T>.isWritable(): Expect<T> = addAssertion(ExpectImpl.path.
* 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.
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*
@@ -320,7 +320,7 @@ fun <T : Path> Expect<T>.isRegularFile(): Expect<T> = addAssertion(ExpectImpl.pa
* 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.
* This assertion is not atomic with respect to concurrent file system operations on the paths the assertion works on.
* Its result, in particular its extended explanations, may be wrong if such concurrent file system operations
* take place.
*

View File

@@ -3,7 +3,7 @@
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.hasDirectoryEntry
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
@@ -26,15 +26,18 @@ class PathAssertionsSpec : ch.tutteli.atrium.specs.integration.PathAssertionsSpe
fun0(Expect<Path>::isDirectory),
fun0(Expect<Path>::isAbsolute), // checks the new function from fluent-jvm because it is not implemented in fluent-jkd8
fun0(Expect<Path>::isRelative), // checks the new function from fluent-jvm because it is not implemented in fluent-jkd8
fun2<Path, String, Array<out String>>(Expect<Path>::contains), // checks the new function from fluent-jvm because it is not implemented in fluent-jkd8
Expect<Path>::hasDirectoryEntry.name to Companion::hasDirectoryEntrySingle, // checks the new function from fluent-jvm because it is not implemented in fluent-jkd8
fun2<Path, String, Array<out String>>(Expect<Path>::hasDirectoryEntry), // checks the new function from fluent-jvm because it is not implemented in fluent-jkd8
fun1(Expect<Path>::hasSameBinaryContentAs),
fun3(Expect<Path>::hasSameTextualContentAs),
fun1(Companion::hasSameTextualContentAsDefaultArgs)
Expect<Path>::hasSameTextualContentAs.name to Companion::hasSameTextualContentAsDefaultArgs
) {
companion object {
private fun hasSameTextualContentAsDefaultArgs(expect: Expect<Path>, targetPath: Path): Expect<Path> =
expect.hasSameTextualContentAs(targetPath)
private fun hasDirectoryEntrySingle(expect: Expect<Path>, entry: String) = expect.hasDirectoryEntry(entry)
}
@Suppress("unused", "UNUSED_VALUE")

View File

@@ -45,7 +45,8 @@ abstract class PathAssertionsSpec(
isDirectory: Fun0<Path>,
isAbsolute: Fun0<Path>,
isRelative: Fun0<Path>,
contains: Fun2<Path, String, Array<out String>>,
hasDirectoryEntrySingle: Fun1<Path, String>,
hasDirectoryEntryMulti: Fun2<Path, String, Array<out String>>,
hasSameBinaryContentAs: Fun1<Path, Path>,
hasSameTextualContentAs: Fun3<Path, Path, Charset, Charset>,
hasSameTextualContentAsDefaultArgs: Fun1<Path, Path>,
@@ -67,7 +68,8 @@ abstract class PathAssertionsSpec(
isDirectory.forSubjectLess(),
isAbsolute.forSubjectLess(),
isRelative.forSubjectLess(),
contains.forSubjectLess("a", arrayOf("b", "c")),
hasDirectoryEntrySingle.forSubjectLess("a"),
hasDirectoryEntryMulti.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"))
@@ -870,100 +872,140 @@ abstract class PathAssertionsSpec(
}
}
describeFun(contains) {
val containsFun = contains.lambda
val hasDirectoryEntryVariations = listOf(
DirectoryEntryVariation("directory", "directories") { entry -> newDirectory(entry) },
DirectoryEntryVariation("file", "files") { entry -> newFile(entry) },
DirectoryEntryVariation("symlink with existing target", "symlinks with existing targets") { entry ->
newFile("$entry-target").createSymbolicLink(resolve(entry))
},
DirectoryEntryVariation("symlink with non-existing target", "symlinks with non-existing targets") { entry ->
resolve("$entry-not-existing-target").createSymbolicLink(resolve(entry))
}
)
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())
}
describeFun(hasDirectoryEntrySingle) {
val hasDirectoryEntryFun = hasDirectoryEntrySingle.lambda
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"))
}
hasDirectoryEntryVariations.forEach { (singleName, _, createEntry) ->
it("does not throw if the parameter is a child $singleName") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
folder.createEntry("a")
expect(folder).hasDirectoryEntryFun("a")
}
}
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("throws if the parameter does not exist") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
expect {
expect(folder).hasDirectoryEntryFun("fileA")
}.toThrow<AssertionError>().message {
contains("${TO.getDefault()}: ${EXIST.getDefault()}")
contains("fileA")
}
}
}
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<AssertionError>().message {
contains("${TO.getDefault()}: ${EXIST.getDefault()}")
contains("file1")
containsNot("file2")
containsNot("file3")
}
}
describeFun(hasDirectoryEntryMulti) {
val hasDirectoryEntryFun = hasDirectoryEntryMulti.lambda
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<AssertionError>().message {
contains("${TO.getDefault()}: ${EXIST.getDefault()}")
contains("file2")
containsNot("file1")
containsNot("file3")
}
}
hasDirectoryEntryVariations.forEach { (singleName, multiName, createEntry) ->
it("does not throw if the single parameter is a child $singleName") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
folder.createEntry("a")
expect(folder).hasDirectoryEntryFun("a", emptyArray())
}
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<AssertionError>().message {
contains("${TO.getDefault()}: ${EXIST.getDefault()}")
containsNot("file2")
containsNot("file1")
contains("file3")
}
}
it("does not throw if two parameters are child $multiName") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
folder.createEntry("a")
folder.createEntry("b")
expect(folder).hasDirectoryEntryFun("a", arrayOf("b"))
}
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<AssertionError>().message {
contains("${TO.getDefault()}: ${EXIST.getDefault()}")
containsNot("file2")
contains("file1")
contains("file3")
}
}
it("does not throw if three parameters are child $multiName") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
folder.createEntry("a")
folder.createEntry("b")
folder.createEntry("c")
expect(folder).hasDirectoryEntryFun("a", arrayOf("b", "c"))
}
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()}"
it("it throws if the first $singleName does not exist") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
folder.createEntry("file2")
folder.createEntry("file3")
expect {
expect(folder).hasDirectoryEntryFun("file1", arrayOf("file2", "file3"))
}.toThrow<AssertionError>().message {
contains("${TO.getDefault()}: ${EXIST.getDefault()}")
contains("file1")
containsNot("file2")
containsNot("file3")
}
}
expect {
expect(folder).containsFun("file1", arrayOf("file2", "file3"))
}.toThrow<AssertionError>().message {
contains(expectedMessage, FAILURE_DUE_TO_NO_SUCH_FILE.getDefault())
containsExplanationFor(maybeLink)
}
}
it("it throws if the second $singleName does not exist") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
folder.createEntry("file1")
folder.createEntry("file3")
expect {
expect(folder).hasDirectoryEntryFun("file1", arrayOf("file2", "file3"))
}.toThrow<AssertionError>().message {
contains("${TO.getDefault()}: ${EXIST.getDefault()}")
contains("file2")
containsNot("file1")
containsNot("file3")
}
}
it("it throws if the third $singleName does not exist") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
folder.createEntry("file1")
folder.createEntry("file2")
expect {
expect(folder).hasDirectoryEntryFun("file1", arrayOf("file2", "file3"))
}.toThrow<AssertionError>().message {
contains("${TO.getDefault()}: ${EXIST.getDefault()}")
containsNot("file2")
containsNot("file1")
contains("file3")
}
}
it("it throws if the first and third $multiName do not exist") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
folder.createEntry("file2")
expect {
expect(folder).hasDirectoryEntryFun("file1", arrayOf("file2", "file3"))
}.toThrow<AssertionError>().message {
contains("${TO.getDefault()}: ${EXIST.getDefault()}")
containsNot("file2")
contains("file1")
contains("file3")
}
}
}
it("does not throw when using all entry types") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("startDir"))
hasDirectoryEntryVariations.first().createEntry(folder, "file1")
hasDirectoryEntryVariations.drop(1).forEachIndexed { index, variation ->
variation.createEntry(folder, "file${index + 2}")
}
val otherEntries = (2..(hasDirectoryEntryVariations.size)).map { "file$it" }.toTypedArray()
expect(folder).hasDirectoryEntryFun("file1", otherEntries)
}
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).hasDirectoryEntryFun("file1", arrayOf("file2", "file3"))
}.toThrow<AssertionError>().message {
contains(expectedMessage, FAILURE_DUE_TO_NO_SUCH_FILE.getDefault())
containsExplanationFor(maybeLink)
}
}
}
@@ -1339,4 +1381,8 @@ private fun expectedPermissionTypeHintFor(type: Translatable, being: Translatabl
being.getDefault()
)
internal data class ContainsTestData(val singleName: String, val multipleName: String, val factory: (f: Path, name: String) -> Path)
internal data class DirectoryEntryVariation(
val singleName: String,
val multipleName: String,
val createEntry: Path.(entry: String) -> Path
)