add isEmptyDirectory (#831)

* add isEmptyDirectory to PathAssertions.kt and implemented it.
* add infix samples.
* add fluent samples.
This commit is contained in:
Mai
2021-03-16 23:08:57 +11:00
committed by GitHub
parent a9f8776942
commit aed87e4a9b
13 changed files with 178 additions and 4 deletions

View File

@@ -280,6 +280,9 @@ fun <T : Path> Expect<T>.isDirectory(): Expect<T> =
* @return an [Expect] for the subject of `this` expectation.
*
* @since 0.16.0
*
* @sample ch.tutteli.atrium.api.fluent.en_GB.samples.PathAssertionSamples.isASymbolicLink
* @sample ch.tutteli.atrium.api.fluent.en_GB.samples.PathAssertionSamples.isNotASymbolicLink
*/
fun <T : Path> Expect<T>.toBeASymbolicLink(): Expect<T> =
_logicAppend { toBeASymbolicLink() }
@@ -379,3 +382,17 @@ fun <T : Path> Expect<T>.hasSameTextualContentAs(
*/
fun <T : Path> Expect<T>.hasSameBinaryContentAs(targetPath: Path): Expect<T> =
_logicAppend { hasSameBinaryContentAs(targetPath) }
/**
* Expects that the subject of `this` expectation (a [Path]) is an empty directory;
* meaning that there is a file system entry at the location the [Path] points to and that is an empty directory.
*
* @return an [Expect] for the subject of `this` expectation.
*
* @since 0.16.0
*
* @sample ch.tutteli.atrium.api.fluent.en_GB.samples.PathAssertionSamples.isEmptyDirectory
* @sample ch.tutteli.atrium.api.fluent.en_GB.samples.PathAssertionSamples.isNotEmptyDirectory
*/
fun <T : Path> Expect<T>.isEmptyDirectory(): Expect<T> =
_logicAppend { isEmptyDirectory() }

View File

@@ -20,6 +20,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
fun0(Expect<Path>::toBeASymbolicLink),
fun0(Expect<Path>::isAbsolute),
fun0(Expect<Path>::isRelative),
fun0(Expect<Path>::isEmptyDirectory),
Expect<Path>::hasDirectoryEntry.name to Companion::hasDirectoryEntrySingle,
fun2<Path, String, Array<out String>>(Expect<Path>::hasDirectoryEntry),
fun1(Expect<Path>::hasSameBinaryContentAs),

View File

@@ -1,7 +1,9 @@
package ch.tutteli.atrium.api.fluent.en_GB.samples
import ch.tutteli.atrium.api.fluent.en_GB.isEmptyDirectory
import ch.tutteli.atrium.api.fluent.en_GB.toBeASymbolicLink
import ch.tutteli.atrium.api.verbs.internal.expect
import ch.tutteli.niok.newDirectory
import ch.tutteli.niok.newFile
import java.nio.file.Files
import kotlin.test.Test
@@ -28,4 +30,19 @@ class PathAssertionSamples {
expect(path).toBeASymbolicLink()
}
}
@Test
fun isEmptyDirectory() {
val path = tempDir.newDirectory("dir")
expect(path).isEmptyDirectory()
}
@Test
fun isNotEmptyDirectory() {
tempDir.newFile("a")
fails {
expect(tempDir).isEmptyDirectory()
}
}
}

View File

@@ -22,6 +22,12 @@ object aRegularFile : Keyword
*/
object aDirectory : Keyword
/**
* A helper construct to allow expressing assertions about a path being an empty directory.
* It can be used for a parameterless function so that it has one parameter and thus can be used as infix function.
*/
object anEmptyDirectory : Keyword
/**
* A helper construct to allow expressing assertions about a path being a symbolic link.
* It can be used for a parameterless function so that it has one parameter and thus can be used as infix function.

View File

@@ -10,7 +10,6 @@ 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
@@ -343,6 +342,9 @@ infix fun <T : Path> Expect<T>.toBe(@Suppress("UNUSED_PARAMETER") aDirectory: aD
* @return an [Expect] for the subject of `this` expectation.
*
* @since 0.16.0
*
* @sample ch.tutteli.atrium.api.infix.en_GB.samples.PathAssertionSamples.isASymbolicLink
* @sample ch.tutteli.atrium.api.infix.en_GB.samples.PathAssertionSamples.isNotASymbolicLink
*/
infix fun <T : Path> Expect<T>.toBe(@Suppress("UNUSED_PARAMETER") aSymbolicLink: aSymbolicLink): Expect<T> =
_logicAppend { toBeASymbolicLink() }
@@ -447,3 +449,17 @@ infix fun <T : Path> Expect<T>.hasSameTextualContentAs(pathWithEncoding: PathWit
*/
infix fun <T : Path> Expect<T>.hasSameBinaryContentAs(targetPath: Path): Expect<T> =
_logicAppend { hasSameBinaryContentAs(targetPath) }
/**
* Expects that the subject of `this` expectation (a [Path]) is an empty directory;
* meaning that there is a file system entry at the location the [Path] points to and that is an empty directory.
*
* @return an [Expect] for the subject of `this` expectation.
*
* @since 0.16.0
*
* @sample ch.tutteli.atrium.api.infix.en_GB.samples.PathAssertionSamples.isEmptyDirectory
* @sample ch.tutteli.atrium.api.infix.en_GB.samples.PathAssertionSamples.isNotEmptyDirectory
*/
infix fun <T : Path> Expect<T>.toBe(@Suppress("UNUSED_PARAMETER") anEmptyDirectory: anEmptyDirectory): Expect<T> =
_logicAppend { isEmptyDirectory() }

View File

@@ -21,6 +21,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
"toBe ${aSymbolicLink::class.simpleName}" to Companion::toBeASymbolicLink,
"toBe ${relative::class.simpleName}" to Companion::isAbsolute,
"toBe ${relative::class.simpleName}" to Companion::isRelative,
"toBe ${relative::class.simpleName}" to Companion::isEmptyDirectory,
fun1(Expect<Path>::hasDirectoryEntry),
"has ${::directoryEntries.name}" to Companion::hasDirectoryEntryMultiple,
fun1(Expect<Path>::hasSameBinaryContentAs),
@@ -50,6 +51,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
private fun toBeASymbolicLink(expect: Expect<Path>) = expect toBe aSymbolicLink
private fun isAbsolute(expect: Expect<Path>) = expect toBe absolute
private fun isRelative(expect: Expect<Path>) = expect toBe relative
private fun isEmptyDirectory(expect: Expect<Path>) = expect toBe anEmptyDirectory
private fun hasDirectoryEntryMultiple(expect: Expect<Path>, entry: String, vararg otherEntries: String) =
expect has directoryEntries(entry, *otherEntries)

View File

@@ -1,8 +1,10 @@
package ch.tutteli.atrium.api.infix.en_GB.samples
import ch.tutteli.atrium.api.infix.en_GB.aSymbolicLink
import ch.tutteli.atrium.api.infix.en_GB.anEmptyDirectory
import ch.tutteli.atrium.api.infix.en_GB.toBe
import ch.tutteli.atrium.api.verbs.internal.expect
import ch.tutteli.niok.newDirectory
import ch.tutteli.niok.newFile
import java.nio.file.Files
import kotlin.test.Test
@@ -29,4 +31,19 @@ class PathAssertionSamples {
expect(path) toBe aSymbolicLink
}
}
@Test
fun isEmptyDirectory() {
val path = tempDir.newDirectory("dir")
expect(path) toBe anEmptyDirectory
}
@Test
fun isNotEmptyDirectory() {
tempDir.newFile("a")
fails {
expect(tempDir) toBe anEmptyDirectory
}
}
}

View File

@@ -50,6 +50,7 @@ fun <T : Path> AssertionContainer<T>.parent(): FeatureExtractorBuilder.Execution
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)
fun <T : Path> AssertionContainer<T>.isEmptyDirectory(): Assertion = impl.isEmptyDirectory(this)
@Suppress("DEPRECATION" /* OptIn is only available since 1.3.70 which we cannot use if we want to support 1.2 */)
@UseExperimental(ExperimentalNewExpectTypes::class)

View File

@@ -52,4 +52,5 @@ interface PathAssertions {
): FeatureExtractorBuilder.ExecutionStep<T, Path>
fun <T : Path> hasDirectoryEntry(container: AssertionContainer<T>, entries: List<String>): Assertion
fun <T : Path> isEmptyDirectory(container: AssertionContainer<T>): Assertion
}

View File

@@ -8,14 +8,13 @@ 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.assertions.builders.withFailureHintBasedOnDefinedSubject
import ch.tutteli.atrium.core.None
import ch.tutteli.atrium.core.Some
import ch.tutteli.atrium.creating.AssertionContainer
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.logic.*
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.logic.creating.filesystem.*
import ch.tutteli.atrium.logic.creating.filesystem.hints.*
import ch.tutteli.atrium.logic.creating.filesystem.runCatchingIo
import ch.tutteli.atrium.logic.creating.transformers.FeatureExtractorBuilder
@@ -194,4 +193,31 @@ class DefaultPathAssertions : PathAssertions {
container.resolve(entry).collect { _logicAppend { exists(NOFOLLOW_LINKS) } }
}
).build()
override fun <T : Path> isEmptyDirectory(container: AssertionContainer<T>): Assertion {
val isDirectory = container.isDirectory()
if (isDirectory.holds()) {
return container.changeSubject.unreported {
it.runCatchingIo { Files.newDirectoryStream(it).use { stream -> stream.firstOrNull() } }
}.let { expectResult ->
assertionBuilder.descriptive.withTest(expectResult) { it is Success && it.value == null }
.withFailureHintBasedOnDefinedSubject(expectResult) {
explainForResolvedLink(it.path) { realPath ->
when (it) {
is Success ->
assertionBuilder.descriptive.failing
.withDescriptionAndRepresentation(
DIRECTORY_CONTAINS,
it.path.relativize(it.value!!)
)
.build()
is Failure -> hintForIoException(realPath, it.exception)
}
}
}
.withDescriptionAndRepresentation(DescriptionBasic.IS, AN_EMPTY_DIRECTORY)
.build()
}
} else return isDirectory
}
}

View File

@@ -42,6 +42,7 @@ abstract class PathExpectationsSpec(
isSymbolicLink: Fun0<Path>,
isAbsolute: Fun0<Path>,
isRelative: Fun0<Path>,
isEmptyDirectory: Fun0<Path>,
hasDirectoryEntrySingle: Fun1<Path, String>,
hasDirectoryEntryMulti: Fun2<Path, String, Array<out String>>,
hasSameBinaryContentAs: Fun1<Path, Path>,
@@ -76,6 +77,7 @@ abstract class PathExpectationsSpec(
isSymbolicLink.forSubjectLess(),
isAbsolute.forSubjectLess(),
isRelative.forSubjectLess(),
isEmptyDirectory.forSubjectLess(),
hasDirectoryEntrySingle.forSubjectLess("a"),
hasDirectoryEntryMulti.forSubjectLess("a", arrayOf("b", "c")),
hasSameBinaryContentAs.forSubjectLess(Paths.get("a")),
@@ -948,6 +950,70 @@ abstract class PathExpectationsSpec(
}
}
describeFun(isEmptyDirectory) {
val isEmptyDirectoryFun = isEmptyDirectory.lambda
val expectedMessage = "$isDescr: ${A_DIRECTORY.getDefault()}"
val expectedEmptyMessage = "$isDescr: ${AN_EMPTY_DIRECTORY.getDefault()}"
context("not accessible") {
it("throws an AssertionError for a non-existent path") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.tmpDir.resolve("nonExistent"))
expect {
expect(file).isEmptyDirectoryFun()
}.toThrow<AssertionError>().message {
contains(expectedMessage, FAILURE_DUE_TO_NO_SUCH_FILE.getDefault())
containsExplanationFor(maybeLink)
}
}
itPrintsFileAccessProblemDetails { testFile ->
expect(testFile).isEmptyDirectoryFun()
}
}
it("throws an AssertionError for a file") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.newFile("test"))
expect {
expect(file).isEmptyDirectoryFun()
}.toThrow<AssertionError>().message {
contains(expectedMessage, "${WAS.getDefault()}: ${A_FILE.getDefault()}")
containsExplanationFor(maybeLink)
}
}
it("throws an AssertionError for a non-empty directory") withAndWithoutSymlink { maybeLink ->
val dir = tempFolder.newDirectory("notEmpty")
dir.newFile("a")
val folder = maybeLink.create(dir)
expect {
expect(folder).isEmptyDirectoryFun()
}.toThrow<AssertionError>().message {
contains(expectedEmptyMessage)
containsExplanationFor(maybeLink)
contains("a")
}
}
it("throws an AssertionError for a directory that contains an empty directory") withAndWithoutSymlink
{ maybeLink ->
val dir = tempFolder.newDirectory("notEmpty")
dir.newDirectory("a")
val folder = maybeLink.create(dir)
expect {
expect(folder).isEmptyDirectoryFun()
}.toThrow<AssertionError>().message {
contains(expectedEmptyMessage)
containsExplanationFor(maybeLink)
contains("a")
}
}
it("does not throw for an empty directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("test"))
expect(folder).isEmptyDirectoryFun()
}
}
val hasDirectoryEntryVariations = listOf(
DirectoryEntryVariation("directory", "directories") { entry -> newDirectory(entry) },
DirectoryEntryVariation("file", "files") { entry -> newFile(entry) },

View File

@@ -29,6 +29,8 @@ enum class DescriptionPathAssertion(override val value: String) : StringBasedTra
EXECUTABLE("ausführbar"),
A_FILE("eine Datei"),
A_DIRECTORY("ein Verzeichnis"),
AN_EMPTY_DIRECTORY("ein leeres Verzeichnis"),
DIRECTORY_CONTAINS("Verzeichnis enthält"),
A_SYMBOLIC_LINK("eine symbolische Verknüpfung"),
A_UNKNOWN_FILE_TYPE("ein unbekannter Dateityp"),
FAILURE_DUE_TO_NO_SUCH_FILE("es exisitiert kein Dateisystemeintrag an diesem Ort"),

View File

@@ -24,6 +24,8 @@ enum class DescriptionPathAssertion(override val value: String) : StringBasedTra
EXECUTABLE("executable"),
A_FILE("a file"),
A_DIRECTORY("a directory"),
AN_EMPTY_DIRECTORY("an empty directory"),
DIRECTORY_CONTAINS("directory contains"),
A_SYMBOLIC_LINK("a symbolic link"),
A_UNKNOWN_FILE_TYPE("a unknown file type"),
FAILURE_DUE_TO_NO_SUCH_FILE("no file system entry exists at this location"),