Skip to content

Commit

Permalink
#942 add Path.notToBeReadable
Browse files Browse the repository at this point in the history
  • Loading branch information
botex98 committed Oct 23, 2021
1 parent 4602f92 commit 61c9b62
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ThrowableExpectationSamples {
}

fails { // because wrong type expected (IllegalStateException instead of IndexOutOfBoundsException), but since we use a block...
expect(IllegalStateException(IndexOutOfBoundsException("abc"))).cause<IllegalStateException> {
expect(IllegalStateException(IndexOutOfBoundsException("abc"))).cause<IllegalStateException> {
messageToContain("b") // ... reporting mentions that subject's message was expected `to contain: "b"`
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,28 @@ fun <T : Path> Expect<T>.notToExist(): Expect<T> =
fun <T : Path> Expect<T>.toBeReadable(): Expect<T> =
_logicAppend { isReadable() }

/**
* Expects that the subject of `this` expectation (a [Path]) is not readable;
* meaning that there is a file system entry at the location the [Path] points to and
* that the current thread does not have the permission to read from it.
*
* 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 assertion 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 subject of `this` expectation.
*
* @sample ch.tutteli.atrium.api.fluent.en_GB.samples.PathExpectationSamples.notToBeReadable
*
* @since 0.17.0
*/
fun <T : Path> Expect<T>.notToBeReadable(): Expect<T> =
_logicAppend { isNotReadable() }

/**
* Expects that the subject of `this` expectation (a [Path]) is writable;
* meaning that there is a file system entry at the location the [Path] points to and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
fun1(Expect<Path>::toEndWith),
fun1(Expect<Path>::notToEndWith),
fun0(Expect<Path>::toBeReadable),
fun0(Expect<Path>::notToBeReadable),
fun0(Expect<Path>::toBeWritable),
fun0(Expect<Path>::notToBeWritable),
fun0(Expect<Path>::toBeExecutable),
Expand Down Expand Up @@ -58,6 +59,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
a1.toEndWith(Paths.get("a"))
a1.notToEndWith(Paths.get("a"))
a1.toBeReadable()
a1.notToBeReadable()
a1.toBeWritable()
a1.notToBeWritable()
a1.toBeExecutable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,39 @@ class PathExpectationSamples {
}
}

@Test
fun notToBeReadable() {
assertIf(ifPosixSupported) {
val writeOnlyPermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("-w--w--w-"))
val readyWritePermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-rw-rw-"))

val writeOnlyDir = tempDir.newDirectory("write_only_dir", writeOnlyPermissions)
val writeOnlyFile = tempDir.newFile("write_only_file", writeOnlyPermissions)
val readWriteDir = tempDir.newDirectory("read_write_dir", readyWritePermissions)
val readWriteFile = tempDir.newFile("read_write_file", readyWritePermissions)

expect(writeOnlyDir).notToBeReadable()
expect(writeOnlyFile).notToBeReadable()

fails {
expect(readWriteDir).notToBeReadable()
}
fails {
expect(readWriteFile).notToBeReadable()
}

fails {
expect(readWriteDir).notToBeReadable()
}
}

fails {
expect(Paths.get("non_existing_dir")).notToBeReadable()
}
}

@Test
fun toBeWritable() {
val dir = tempDir.newDirectory("test_dir")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ fun <E> path(path: String, assertionCreator: Expect<E>.() -> Unit): PathWithCrea
infix fun <T : Path> Expect<T>.toBe(@Suppress("UNUSED_PARAMETER") readable: readable): Expect<T> =
_logicAppend { isReadable() }


//TODO move to pathExpectations.kt with 0.18.0
/**
* Expects that the subject of `this` expectation (a [Path]) is writable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,28 @@ infix fun <T : Path> Expect<T>.toHaveTheSameTextualContentAs(pathWithEncoding: P
infix fun <T : Path> Expect<T>.toHaveTheSameBinaryContentAs(targetPath: Path): Expect<T> =
_logicAppend { hasSameBinaryContentAs(targetPath) }

/**
* Expects that the subject of `this` expectation (a [Path]) is not readable;
* meaning that there is a file system entry at the location the [Path] points to and
* that the current thread does not have the permission to read from it.
*
* 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 assertion 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 subject of `this` expectation.
*
* @sample ch.tutteli.atrium.api.infix.en_GB.samples.PathExpectationSamples.notToBeReadable
*
* @since 0.17.0
*/
infix fun <T : Path> Expect<T>.notToBe(@Suppress("UNUSED_PARAMETER") readable: readable): Expect<T> =
_logicAppend { isNotReadable() }

/**
* Expects that the subject of `this` expectation (a [Path]) is not writable;
* meaning that there is a file system entry at the location the [Path] points to and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
fun1(Expect<Path>::toEndWith),
fun1(Expect<Path>::notToEndWith),
"toBe ${readable::class.simpleName}" to Companion::toBeReadable,
"notToBe ${readable::class.simpleName}" to Companion::notToBeReadable,
"toBe ${writable::class.simpleName}" to Companion::toBeWritable,
"notToBe ${writable::class.simpleName}" to Companion::notToBeWritable,
"toBe ${executable::class.simpleName}" to Companion::toBeExecutable,
Expand Down Expand Up @@ -45,6 +46,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
private fun toExist(expect: Expect<Path>) = expect toBe existing
private fun notToExist(expect: Expect<Path>) = expect notToBe existing
private fun toBeReadable(expect: Expect<Path>) = expect toBe readable
private fun notToBeReadable(expect: Expect<Path>) = expect notToBe readable
private fun toBeWritable(expect: Expect<Path>) = expect toBe writable
private fun notToBeWritable(expect: Expect<Path>) = expect notToBe writable
private fun toBeExecutable(expect: Expect<Path>) = expect toBe executable
Expand Down Expand Up @@ -91,6 +93,7 @@ class PathExpectationsSpec : ch.tutteli.atrium.specs.integration.PathExpectation
a1 toEndWith Paths.get("a")
a1 notToEndWith Paths.get("a")
a1 toBe readable
a1 notToBe readable
a1 toBe writable
a1 notToBe writable
a1 toBe executable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,35 @@ class PathExpectationSamples {
}
}

@Test
fun notToBeReadable() {
assertIf(fileSystemSupportsPosixPermissions()) {
val writeOnlyPermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("-w--w--w-"))
val readWritePermissions =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-rw-rw-"))

val writeOnlyDir = tempDir.newDirectory("write_only_dir", writeOnlyPermissions)
val writeOnlyFile = tempDir.newFile("write_only_file", writeOnlyPermissions)
val readWriteDir = tempDir.newDirectory("read_write_dir", readWritePermissions)
val readWriteFile = tempDir.newFile("read_write_file", readWritePermissions)

expect(writeOnlyDir) notToBe readable
expect(writeOnlyFile) notToBe readable

fails {
expect(readWriteDir) notToBe readable
}
fails {
expect(readWriteFile) notToBe readable
}
}

fails {
expect(Paths.get("non_existing_dir")) notToBe readable
}
}

@Test
fun toBeWritable() {
val dir = tempDir.newDirectory("test_dir")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fun <T : Path> AssertionContainer<T>.exists(linkOption: LinkOption? = null): Ass
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>.isNotReadable(): Assertion = impl.isNotReadable(this)
fun <T : Path> AssertionContainer<T>.isWritable(): Assertion = impl.isWritable(this)
fun <T : Path> AssertionContainer<T>.isNotWritable(): Assertion = impl.isNotWritable(this)
fun <T : Path> AssertionContainer<T>.isExecutable(): Assertion = impl.isExecutable(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface PathAssertions {
fun <T : Path> existsNot(container: AssertionContainer<T>, linkOption: LinkOption? = null): Assertion

fun <T : Path> isReadable(container: AssertionContainer<T>): Assertion
fun <T : Path> isNotReadable(container: AssertionContainer<T>): Assertion
fun <T : Path> isWritable(container: AssertionContainer<T>): Assertion
fun <T : Path> isNotWritable(container: AssertionContainer<T>): Assertion
fun <T : Path> isExecutable(container: AssertionContainer<T>): Assertion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ class DefaultPathAssertions : PathAssertions {
override fun <T : Path> isReadable(container: AssertionContainer<T>): Assertion =
filePermissionAssertion(container, READABLE, AccessMode.READ, DescriptionBasic.IS, shouldHaveAccess = true)

override fun <T : Path> isNotReadable(container: AssertionContainer<T>): Assertion =
filePermissionAssertion(
container,
READABLE,
AccessMode.READ,
DescriptionBasic.IS_NOT,
shouldHaveAccess = false
)

override fun <T : Path> isWritable(container: AssertionContainer<T>): Assertion =
filePermissionAssertion(container, WRITABLE, AccessMode.WRITE, DescriptionBasic.IS, shouldHaveAccess = true)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ abstract class PathExpectationsSpec(
toEndWith: Fun1<Path, Path>,
notToEndWith: Fun1<Path, Path>,
toBeReadable: Fun0<Path>,
notToBeReadable: Fun0<Path>,
toBeWritable: Fun0<Path>,
notToBeWritable: Fun0<Path>,
toBeExecutable: Fun0<Path>,
Expand Down Expand Up @@ -71,6 +72,7 @@ abstract class PathExpectationsSpec(
toEndWith.forSubjectLess(Paths.get("a")),
notToEndWith.forSubjectLess(Paths.get("a")),
toBeReadable.forSubjectLess(),
notToBeReadable.forSubjectLess(),
toBeWritable.forSubjectLess(),
notToBeWritable.forSubjectLess(),
toBeExecutable.forSubjectLess(),
Expand Down Expand Up @@ -540,6 +542,115 @@ abstract class PathExpectationsSpec(
}
}

describeFun(notToBeReadable) {
val notToBeReadableFun = notToBeReadable.lambda
val expectedMessage = "$isNotDescr: ${READABLE.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).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(
expectedMessage,
FAILURE_DUE_TO_NO_SUCH_FILE.getDefault()
)
containsExplanationFor(maybeLink)
}
}

itPrintsFileAccessProblemDetails(
// because if we cannot access parent then it is still not readable
checkParentHints = false
) { testFile ->
expect(testFile).notToBeReadableFun()
}
}

context("POSIX: readable", skip = ifPosixNotSupported) {
it("throws an AssertionError for a file") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.newFile("readable"))

file.whileWithPermissions(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, OTHERS_EXECUTE) {
expect {
expect(file).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(expectedMessage)
}
}
}

it("throws an AssertionError for a directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("readable"))

folder.whileWithPermissions(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, OTHERS_EXECUTE) {
expect {
expect(folder).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(expectedMessage)
}
}
}
}

context("POSIX: not readable", skip = ifPosixNotSupported) {
it("does not throw for a file") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.newFile("not-readable"))
file.whileWithPermissions(OWNER_WRITE, OWNER_EXECUTE, OTHERS_EXECUTE) {
expect(file).notToBeReadableFun()
}
}

it("does not throw for a directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("not-readable"))
folder.whileWithPermissions(OWNER_WRITE, OWNER_EXECUTE, OTHERS_EXECUTE) {
expect(folder).notToBeReadableFun()
}
}
}

context("ACL: not writable", skip = ifAclNotSupported) {
it("does not throw for a file") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.newFile("not-readable"))
file.whileWithAcl(TestAcls::ownerNoRead) {
expect(file).notToBeReadableFun()
}
}

it("does not throw for a directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("not-readable"))
folder.whileWithAcl(TestAcls::ownerNoRead) {
expect(folder).notToBeReadableFun()
}
}
}

context("ACL: writable", skip = ifAclNotSupported) {
it("throws an AssertionError for a file") withAndWithoutSymlink { maybeLink ->
val file = maybeLink.create(tempFolder.newFile("readable"))
file.whileWithAcl(TestAcls::all) {
expect {
expect(file).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(expectedMessage)
}
}
}

it("throws an AssertionError for a directory") withAndWithoutSymlink { maybeLink ->
val folder = maybeLink.create(tempFolder.newDirectory("readable"))
folder.whileWithAcl(TestAcls::all) {
expect {
expect(folder).notToBeReadableFun()
}.toThrow<AssertionError>().message {
toContain(expectedMessage)
}
}
}
}
}

describeFun(toBeWritable) {
val toBeWritableFun = toBeWritable.lambda
val expectedMessage = "$isDescr: ${WRITABLE.getDefault()}"
Expand Down

0 comments on commit 61c9b62

Please sign in to comment.